From b20c6ae81fa89ee6fd2394facfd8c0ffe101f744 Mon Sep 17 00:00:00 2001 From: beyondlevi Date: Wed, 7 Oct 2026 10:28:01 -0300 Subject: [PATCH 01/11] server: UHID mouse/keyboard, pointer probe, clipboard (scrcpy 4.1 vendored) Co-Authored-By: Volund OS <287815646+volundos[bot]@users.noreply.github.com> --- .gitignore | 6 + server/build.sh | 45 + server/src/dev/droidbridge/Log.java | 24 + server/src/dev/droidbridge/Main.java | 78 ++ server/src/dev/droidbridge/PointerProbe.java | 124 +++ server/src/dev/droidbridge/Protocol.java | 36 + server/src/dev/droidbridge/Session.java | 338 +++++++ server/src/dev/droidbridge/Uhid.java | 94 ++ server/third_party/SCRCPY-LICENSE | 204 ++++ .../IOnPrimaryClipChangedListener.aidl | 24 + .../android/view/IDisplayWindowListener.aidl | 51 + .../android/content/IContentProvider.java | 5 + .../genymobile/scrcpy/AndroidVersions.java | 32 + .../com/genymobile/scrcpy/AsyncProcessor.java | 18 + .../com/genymobile/scrcpy/CleanUp.java | 271 ++++++ .../com/genymobile/scrcpy/FakeContext.java | 127 +++ .../com/genymobile/scrcpy/Options.java | 707 ++++++++++++++ .../com/genymobile/scrcpy/Server.java | 291 ++++++ .../com/genymobile/scrcpy/Workarounds.java | 296 ++++++ .../genymobile/scrcpy/audio/AudioCapture.java | 20 + .../scrcpy/audio/AudioCaptureException.java | 12 + .../genymobile/scrcpy/audio/AudioCodec.java | 51 + .../genymobile/scrcpy/audio/AudioConfig.java | 29 + .../scrcpy/audio/AudioDirectCapture.java | 142 +++ .../genymobile/scrcpy/audio/AudioEncoder.java | 380 ++++++++ .../scrcpy/audio/AudioPlaybackCapture.java | 138 +++ .../scrcpy/audio/AudioRawRecorder.java | 99 ++ .../scrcpy/audio/AudioRecordReader.java | 67 ++ .../genymobile/scrcpy/audio/AudioSource.java | 45 + .../scrcpy/control/ControlChannel.java | 24 + .../scrcpy/control/ControlMessage.java | 289 ++++++ .../scrcpy/control/ControlMessageReader.java | 200 ++++ .../control/ControlProtocolException.java | 9 + .../genymobile/scrcpy/control/Controller.java | 893 ++++++++++++++++++ .../scrcpy/control/DeviceMessage.java | 59 ++ .../scrcpy/control/DeviceMessageSender.java | 57 ++ .../scrcpy/control/DeviceMessageWriter.java | 47 + .../scrcpy/control/KeyComposition.java | 174 ++++ .../genymobile/scrcpy/control/Pointer.java | 57 ++ .../scrcpy/control/PointersState.java | 105 ++ .../scrcpy/control/PositionMapper.java | 48 + .../scrcpy/control/UhidManager.java | 287 ++++++ .../scrcpy/device/DesktopConnection.java | 178 ++++ .../com/genymobile/scrcpy/device/Device.java | 326 +++++++ .../genymobile/scrcpy/device/Streamer.java | 205 ++++ .../scrcpy/display/DisplayInfo.java | 53 ++ .../scrcpy/display/DisplayMonitor.java | 144 +++ .../scrcpy/display/DisplayProperties.java | 52 + .../display/DisplayPropertiesTracker.java | 84 ++ .../display/DisplayResizeDebouncer.java | 85 ++ .../com/genymobile/scrcpy/model/Codec.java | 24 + .../genymobile/scrcpy/model/CodecOption.java | 112 +++ .../scrcpy/model/ConfigurationException.java | 7 + .../genymobile/scrcpy/model/DeviceApp.java | 26 + .../genymobile/scrcpy/model/NewDisplay.java | 31 + .../genymobile/scrcpy/model/Orientation.java | 54 ++ .../com/genymobile/scrcpy/model/Point.java | 43 + .../com/genymobile/scrcpy/model/Position.java | 61 ++ .../com/genymobile/scrcpy/model/Size.java | 198 ++++ .../scrcpy/opengl/AffineOpenGLFilter.java | 135 +++ .../com/genymobile/scrcpy/opengl/GLUtils.java | 124 +++ .../scrcpy/opengl/OpenGLException.java | 13 + .../scrcpy/opengl/OpenGLFilter.java | 21 + .../scrcpy/opengl/OpenGLRunner.java | 232 +++++ .../genymobile/scrcpy/util/AffineMatrix.java | 368 ++++++++ .../com/genymobile/scrcpy/util/Binary.java | 38 + .../genymobile/scrcpy/util/BinarySearch.java | 53 ++ .../genymobile/scrcpy/util/CodecUtils.java | 38 + .../com/genymobile/scrcpy/util/Command.java | 43 + .../scrcpy/util/HandlerExecutor.java | 23 + .../com/genymobile/scrcpy/util/IO.java | 79 ++ .../com/genymobile/scrcpy/util/Ln.java | 122 +++ .../com/genymobile/scrcpy/util/LogUtils.java | 299 ++++++ .../com/genymobile/scrcpy/util/Settings.java | 38 + .../scrcpy/util/SettingsException.java | 11 + .../genymobile/scrcpy/util/StringUtils.java | 22 + .../com/genymobile/scrcpy/util/Threads.java | 43 + .../scrcpy/video/CameraAspectRatio.java | 37 + .../scrcpy/video/CameraCapture.java | 531 +++++++++++ .../genymobile/scrcpy/video/CameraFacing.java | 33 + .../scrcpy/video/CaptureControl.java | 42 + .../scrcpy/video/NewDisplayCapture.java | 374 ++++++++ .../scrcpy/video/ScreenCapture.java | 228 +++++ .../scrcpy/video/SurfaceCapture.java | 85 ++ .../scrcpy/video/SurfaceEncoder.java | 388 ++++++++ .../genymobile/scrcpy/video/VideoCodec.java | 54 ++ .../scrcpy/video/VideoConstraints.java | 69 ++ .../genymobile/scrcpy/video/VideoFilter.java | 119 +++ .../genymobile/scrcpy/video/VideoSource.java | 22 + .../scrcpy/video/VirtualDisplayListener.java | 7 + .../scrcpy/wrappers/ActivityManager.java | 192 ++++ .../scrcpy/wrappers/ClipboardManager.java | 43 + .../scrcpy/wrappers/ContentProvider.java | 162 ++++ .../scrcpy/wrappers/DisplayControl.java | 82 ++ .../scrcpy/wrappers/DisplayManager.java | 250 +++++ .../wrappers/DisplayWindowListener.java | 37 + .../scrcpy/wrappers/InputManager.java | 146 +++ .../scrcpy/wrappers/PowerManager.java | 80 ++ .../scrcpy/wrappers/ServiceManager.java | 112 +++ .../scrcpy/wrappers/StatusBarManager.java | 97 ++ .../scrcpy/wrappers/SurfaceControl.java | 191 ++++ .../scrcpy/wrappers/WindowManager.java | 267 ++++++ 102 files changed, 13036 insertions(+) create mode 100644 .gitignore create mode 100755 server/build.sh create mode 100644 server/src/dev/droidbridge/Log.java create mode 100644 server/src/dev/droidbridge/Main.java create mode 100644 server/src/dev/droidbridge/PointerProbe.java create mode 100644 server/src/dev/droidbridge/Protocol.java create mode 100644 server/src/dev/droidbridge/Session.java create mode 100644 server/src/dev/droidbridge/Uhid.java create mode 100644 server/third_party/SCRCPY-LICENSE create mode 100644 server/third_party/scrcpy-aidl/android/content/IOnPrimaryClipChangedListener.aidl create mode 100644 server/third_party/scrcpy-aidl/android/view/IDisplayWindowListener.aidl create mode 100644 server/third_party/scrcpy-java/android/content/IContentProvider.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/AndroidVersions.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/AsyncProcessor.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/CleanUp.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/FakeContext.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/Options.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/Server.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/Workarounds.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/audio/AudioCapture.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/audio/AudioCaptureException.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/audio/AudioCodec.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/audio/AudioConfig.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/audio/AudioDirectCapture.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/audio/AudioEncoder.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/audio/AudioPlaybackCapture.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/audio/AudioRawRecorder.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/audio/AudioRecordReader.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/audio/AudioSource.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/control/ControlChannel.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/control/ControlMessage.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/control/ControlMessageReader.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/control/ControlProtocolException.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/control/Controller.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/control/DeviceMessage.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/control/DeviceMessageSender.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/control/DeviceMessageWriter.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/control/KeyComposition.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/control/Pointer.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/control/PointersState.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/control/PositionMapper.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/control/UhidManager.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/device/DesktopConnection.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/device/Device.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/device/Streamer.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/display/DisplayInfo.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/display/DisplayMonitor.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/display/DisplayProperties.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/display/DisplayPropertiesTracker.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/display/DisplayResizeDebouncer.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/model/Codec.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/model/CodecOption.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/model/ConfigurationException.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/model/DeviceApp.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/model/NewDisplay.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/model/Orientation.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/model/Point.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/model/Position.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/model/Size.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/opengl/AffineOpenGLFilter.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/opengl/GLUtils.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/opengl/OpenGLException.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/opengl/OpenGLFilter.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/opengl/OpenGLRunner.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/util/AffineMatrix.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/util/Binary.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/util/BinarySearch.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/util/CodecUtils.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/util/Command.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/util/HandlerExecutor.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/util/IO.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/util/Ln.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/util/LogUtils.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/util/Settings.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/util/SettingsException.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/util/StringUtils.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/util/Threads.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/video/CameraAspectRatio.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/video/CameraCapture.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/video/CameraFacing.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/video/CaptureControl.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/video/NewDisplayCapture.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/video/ScreenCapture.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/video/SurfaceCapture.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/video/SurfaceEncoder.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/video/VideoCodec.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/video/VideoConstraints.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/video/VideoFilter.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/video/VideoSource.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/video/VirtualDisplayListener.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/wrappers/ActivityManager.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/wrappers/ClipboardManager.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/wrappers/ContentProvider.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/wrappers/DisplayControl.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/wrappers/DisplayManager.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/wrappers/DisplayWindowListener.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/wrappers/InputManager.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/wrappers/PowerManager.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/wrappers/ServiceManager.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/wrappers/StatusBarManager.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/wrappers/SurfaceControl.java create mode 100644 server/third_party/scrcpy-java/com/genymobile/scrcpy/wrappers/WindowManager.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e14a6e5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +build/ +.build/ +*.app +dist/ +.DS_Store +.swiftpm/ diff --git a/server/build.sh b/server/build.sh new file mode 100755 index 0000000..b20c88b --- /dev/null +++ b/server/build.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +# Builds droidbridge-server.jar (a dex in a jar) without Gradle, like scrcpy's +# build_without_gradle.sh. Needs ANDROID_HOME with platforms/android-36 and build-tools/36.0.0. +set -euo pipefail + +VERSION=${DROIDBRIDGE_VERSION:-0.1.0} +PLATFORM=${ANDROID_PLATFORM:-36} +BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-36.0.0} +: "${ANDROID_HOME:?set ANDROID_HOME}" + +HERE=$(cd "$(dirname "$0")" && pwd) +OUT=${BUILD_DIR:-$HERE/build} +ANDROID_JAR="$ANDROID_HOME/platforms/android-$PLATFORM/android.jar" +ANDROID_AIDL="$ANDROID_HOME/platforms/android-$PLATFORM/framework.aidl" +TOOLS="$ANDROID_HOME/build-tools/$BUILD_TOOLS" + +rm -rf "$OUT" && mkdir -p "$OUT/classes" "$OUT/gen/com/genymobile/scrcpy" "$OUT/gen/dev/droidbridge" + +cat > "$OUT/gen/com/genymobile/scrcpy/BuildConfig.java" < "$OUT/gen/dev/droidbridge/BuildInfo.java" < "$OUT/sources.txt" +javac -encoding UTF-8 -nowarn -bootclasspath "$ANDROID_JAR" -cp "$TOOLS/core-lambda-stubs.jar" \ + -d "$OUT/classes" -source 1.8 -target 1.8 @"$OUT/sources.txt" 2>&1 | grep -v "^warning\|^Note:\|^1 warning\|^[0-9]* warnings" || true +test -f "$OUT/classes/dev/droidbridge/Main.class" + +# Like scrcpy, the generated/stub android.* classes go into the dex too. +(cd "$OUT/classes" && "$TOOLS/d8" --classpath "$ANDROID_JAR" --output "$OUT/droidbridge-server.jar" --min-api 26 \ + $(find . -name '*.class')) +echo "$OUT/droidbridge-server.jar" diff --git a/server/src/dev/droidbridge/Log.java b/server/src/dev/droidbridge/Log.java new file mode 100644 index 0000000..5f6d6ae --- /dev/null +++ b/server/src/dev/droidbridge/Log.java @@ -0,0 +1,24 @@ +package dev.droidbridge; + +/** Logs to logcat (tag "droidbridge") and to stdout, which the Mac app keeps. */ +final class Log { + private static final String TAG = "droidbridge"; + + private Log() { + } + + static void i(String msg) { + android.util.Log.i(TAG, msg); + System.out.println("I " + msg); + } + + static void w(String msg) { + android.util.Log.w(TAG, msg); + System.out.println("W " + msg); + } + + static void e(String msg, Throwable t) { + android.util.Log.e(TAG, msg, t); + System.out.println("E " + msg + (t != null ? ": " + t : "")); + } +} diff --git a/server/src/dev/droidbridge/Main.java b/server/src/dev/droidbridge/Main.java new file mode 100644 index 0000000..46ef8ac --- /dev/null +++ b/server/src/dev/droidbridge/Main.java @@ -0,0 +1,78 @@ +package dev.droidbridge; + +import android.annotation.SuppressLint; +import android.net.LocalServerSocket; +import android.net.LocalSocket; +import android.os.Looper; +import android.system.Os; + +import com.genymobile.scrcpy.Workarounds; + +import java.lang.reflect.Field; + +/** + * Entry point, started by the Mac app as the shell user: + * {@code CLASSPATH=/data/local/tmp/droidbridge-server.jar app_process / dev.droidbridge.Main } + * Listens on the abstract socket "droidbridge" (reached through {@code adb forward}) for one connection. + */ +public final class Main { + public static final String VERSION = BuildInfo.VERSION; + + private Main() { + } + + public static void main(String... args) { + int status = 0; + try { + run(args); + } catch (Throwable t) { + Log.e("fatal", t); + status = 1; + } finally { + // The Android runtime may leave non-daemon threads behind. + System.exit(status); + } + } + + private static void run(String... args) throws Exception { + if (args.length < 1 || !VERSION.equals(args[0])) { + throw new IllegalArgumentException("server is " + VERSION + ", client asked for " + (args.length > 0 ? args[0] : "nothing")); + } + if (Os.getuid() == 0) { + Os.setuid(2000); // the clipboard does not work as root + } + prepareMainLooper(); + Workarounds.apply(); + + LocalSocket socket; + try (LocalServerSocket server = new LocalServerSocket("droidbridge")) { + Log.i("droidbridge " + VERSION + " waiting for the Mac"); + System.out.println("READY"); + socket = server.accept(); + } + Session session = new Session(socket.getInputStream(), socket.getOutputStream()); + Thread worker = new Thread(() -> { + try { + session.run(); + } catch (Throwable t) { + Log.e("session ended", t); + } finally { + Looper.getMainLooper().quitSafely(); + } + }, "session"); + worker.start(); + Looper.loop(); + socket.close(); + } + + private static void prepareMainLooper() throws ReflectiveOperationException { + // Like Looper.prepareMainLooper(), but the loop may quit. + Looper.prepare(); + synchronized (Looper.class) { + @SuppressLint("DiscouragedPrivateApi") + Field field = Looper.class.getDeclaredField("sMainLooper"); + field.setAccessible(true); + field.set(null, Looper.myLooper()); + } + } +} diff --git a/server/src/dev/droidbridge/PointerProbe.java b/server/src/dev/droidbridge/PointerProbe.java new file mode 100644 index 0000000..b55202d --- /dev/null +++ b/server/src/dev/droidbridge/PointerProbe.java @@ -0,0 +1,124 @@ +package dev.droidbridge; + +import android.os.IBinder; +import android.os.ParcelFileDescriptor; +import android.view.InputDevice; + +import java.io.ByteArrayOutputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Method; +import java.nio.charset.StandardCharsets; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Reads where the mouse pointer is. The shell user cannot monitor input, and the system's + * cursor position API is internal to system_server, but the input service's dump lists each + * device's hovering pointer in display coordinates, e.g. + * {@code DeviceId(15):[... hoveringPointers=[Pointer(id=0, MOUSE) at (0, 1835.57)] ...]}. + * A dump takes about 20 ms on a recent phone, so it is only taken when the pointer moves + * toward the edge that leads back to the Mac. + */ +final class PointerProbe { + static final class Sample { + final float x; + final float y; + final int width; + final int height; + + Sample(float x, float y, int width, int height) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } + + @Override + public String toString() { + return "(" + x + ", " + y + ") in " + width + "x" + height; + } + } + + private static final Pattern FRAME = Pattern.compile("logicalFrame=\\[0, 0, (\\d+), (\\d+)\\]"); + + private final IBinder input; + private int deviceId = -1; + + PointerProbe() throws ReflectiveOperationException { + Class serviceManager = Class.forName("android.os.ServiceManager"); + Method getService = serviceManager.getMethod("getService", String.class); + input = (IBinder) getService.invoke(null, "input"); + if (input == null) { + throw new IllegalStateException("input service not found"); + } + } + + /** Finds the input device id the system gave to our UHID mouse. */ + boolean findDevice(String name, long timeoutMs) throws InterruptedException { + long deadline = System.currentTimeMillis() + timeoutMs; + do { + for (int id : InputDevice.getDeviceIds()) { + InputDevice device = InputDevice.getDevice(id); + if (device != null && name.equals(device.getName())) { + deviceId = id; + Log.i("mouse is input device " + id); + return true; + } + } + Thread.sleep(50); + } while (System.currentTimeMillis() < deadline); + return false; + } + + /** Returns the pointer position, or null if the pointer has not moved yet. */ + Sample sample() throws IOException { + String dump = dump(); + Matcher pointer = Pattern.compile("DeviceId\\(" + deviceId + + "\\):\\[[^\\]]*?hoveringPointers=\\[Pointer\\(id=\\d+, MOUSE\\) at \\(([-\\d.]+), ([-\\d.]+)\\)").matcher(dump); + if (!pointer.find()) { + return null; + } + int width = 0; + int height = 0; + int cursor = dump.indexOf("MouseCursorController"); + Matcher frame = FRAME.matcher(dump); + if (cursor >= 0 && frame.find(cursor) || frame.find(0)) { + width = Integer.parseInt(frame.group(1)); + height = Integer.parseInt(frame.group(2)); + } + return new Sample(Float.parseFloat(pointer.group(1)), Float.parseFloat(pointer.group(2)), width, height); + } + + private String dump() throws IOException { + ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe(); + ByteArrayOutputStream bytes = new ByteArrayOutputStream(256 * 1024); + Thread reader = new Thread(() -> { + try (InputStream in = new FileInputStream(pipe[0].getFileDescriptor())) { + byte[] buf = new byte[64 * 1024]; + int n; + while ((n = in.read(buf)) > 0) { + bytes.write(buf, 0, n); + } + } catch (IOException e) { + Log.w("dump read: " + e.getMessage()); + } + }, "probe-reader"); + reader.start(); + try { + input.dump(pipe[1].getFileDescriptor(), new String[0]); + } catch (Exception e) { + throw new IOException("input dump failed", e); + } finally { + pipe[1].close(); + } + try { + reader.join(1000); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + pipe[0].close(); + return new String(bytes.toByteArray(), StandardCharsets.UTF_8); + } +} diff --git a/server/src/dev/droidbridge/Protocol.java b/server/src/dev/droidbridge/Protocol.java new file mode 100644 index 0000000..43250bc --- /dev/null +++ b/server/src/dev/droidbridge/Protocol.java @@ -0,0 +1,36 @@ +package dev.droidbridge; + +/** + * Wire protocol between the Mac app and this server (see protocol/README.md). + * Every message is: u8 type, u32 payload length (big-endian), payload. + */ +public final class Protocol { + public static final int VERSION = 1; + + // Mac -> Android + public static final int HELLO = 0x01; // u16 protocol version + public static final int ENTER = 0x02; // u8 side, u16 ratio along the edge (0..65535) + public static final int LEAVE = 0x03; // empty + public static final int MOUSE = 0x04; // u8 buttons, i16 dx, i16 dy, i8 wheel, i8 hwheel + public static final int KEYS = 0x05; // 8-byte HID boot keyboard report + public static final int CLIPBOARD = 0x06; // utf-8 text + public static final int PING = 0x07; // empty + + // Android -> Mac + public static final int DEVICE = 0x81; // u16 protocol version, u16 width, u16 height, utf-8 model + public static final int EDGE = 0x82; // u8 side, u16 ratio along the edge + public static final int DEVICE_CLIPBOARD = 0x83; // utf-8 text + public static final int PONG = 0x84; // empty + + // Sides of the Android screen + public static final int SIDE_NONE = -1; + public static final int SIDE_LEFT = 0; + public static final int SIDE_RIGHT = 1; + public static final int SIDE_TOP = 2; + public static final int SIDE_BOTTOM = 3; + + public static final int MAX_PAYLOAD = 1 << 20; + + private Protocol() { + } +} diff --git a/server/src/dev/droidbridge/Session.java b/server/src/dev/droidbridge/Session.java new file mode 100644 index 0000000..4b6dee9 --- /dev/null +++ b/server/src/dev/droidbridge/Session.java @@ -0,0 +1,338 @@ +package dev.droidbridge; + +import android.os.Build; +import android.os.Handler; +import android.os.Looper; + +import com.genymobile.scrcpy.wrappers.ClipboardManager; +import com.genymobile.scrcpy.wrappers.ServiceManager; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** One connection from the Mac: a mouse and a keyboard over UHID, plus the clipboard. */ +final class Session { + static final String MOUSE_NAME = "droidbridge mouse"; + static final String KEYBOARD_NAME = "droidbridge keyboard"; + private static final int VENDOR_ID = 0x18D1; + + /** Counts pushed against the return edge, after it is reached, before control goes back. */ + private static final int PUSH_TO_LEAVE = 24; + /** Minimum time between two pointer probes. */ + private static final long PROBE_INTERVAL_MS = 30; + /** How close to the edge (px) counts as touching it. */ + private static final float EDGE_SLOP = 1.5f; + + private final DataInputStream in; + private final DataOutputStream out; + private final Uhid mouse; + private final Uhid keyboard; + private final PointerProbe probe; + private final ClipboardManager clipboard; + private final ExecutorService prober = Executors.newSingleThreadExecutor(); + + private volatile int returnSide = Protocol.SIDE_NONE; + private volatile boolean atEdge; + private volatile float edgeRatio; + private volatile boolean probing; + private volatile long lastProbe; + private int pushed; + private boolean edgeSent; + private int buttons; + + private final Object clipLock = new Object(); + private String lastClipFromMac; + private String lastClipToMac; + + Session(InputStream input, OutputStream output) throws Exception { + in = new DataInputStream(new BufferedInputStream(input)); + out = new DataOutputStream(new BufferedOutputStream(output)); + mouse = new Uhid(MOUSE_NAME, VENDOR_ID, 0x4D42, Uhid.MOUSE_DESCRIPTOR); + keyboard = new Uhid(KEYBOARD_NAME, VENDOR_ID, 0x4B42, Uhid.KEYBOARD_DESCRIPTOR); + probe = new PointerProbe(); + if (!probe.findDevice(MOUSE_NAME, 3000)) { + throw new IOException("the system did not add the UHID mouse"); + } + clipboard = ServiceManager.getClipboardManager(); + } + + void run() throws IOException { + int[] size = displaySize(); + sendDevice(size[0], size[1]); + if (clipboard != null) { + // Clipboard callbacks need a looper; the main thread runs one. + new Handler(Looper.getMainLooper()).post(() -> clipboard.addPrimaryClipChangedListener(this::onDeviceClipboard)); + } else { + Log.w("no clipboard manager on this device"); + } + try { + while (true) { + int type = in.readUnsignedByte(); + int length = in.readInt(); + if (length < 0 || length > Protocol.MAX_PAYLOAD) { + throw new IOException("bad payload length " + length); + } + byte[] payload = new byte[length]; + in.readFully(payload); + handle(type, payload); + } + } catch (EOFException e) { + Log.i("Mac disconnected"); + } finally { + prober.shutdownNow(); + mouse.close(); + keyboard.close(); + } + } + + private void handle(int type, byte[] p) throws IOException { + switch (type) { + case Protocol.HELLO: + int version = ((p[0] & 0xFF) << 8) | (p[1] & 0xFF); + Log.i("Mac protocol " + version); + break; + case Protocol.MOUSE: + buttons = p[0] & 0x1F; + int dx = (short) (((p[1] & 0xFF) << 8) | (p[2] & 0xFF)); + int dy = (short) (((p[3] & 0xFF) << 8) | (p[4] & 0xFF)); + moveMouse(dx, dy, p[5], p[6]); + onMotion(dx, dy); + break; + case Protocol.KEYS: + if (p.length == 8) { + keyboard.input(p); + } + break; + case Protocol.ENTER: + enter(p[0], (((p[1] & 0xFF) << 8) | (p[2] & 0xFF)) / 65535f); + break; + case Protocol.LEAVE: + returnSide = Protocol.SIDE_NONE; + buttons = 0; + mouse.input(new byte[5]); + keyboard.input(new byte[8]); + break; + case Protocol.CLIPBOARD: + setDeviceClipboard(new String(p, StandardCharsets.UTF_8)); + break; + case Protocol.PING: + send(Protocol.PONG, new byte[0]); + break; + default: + Log.w("unknown message type " + type); + } + } + + /** Sends a relative move as UHID reports, each axis limited to -127..127 per report. */ + private void moveMouse(int dx, int dy, int wheel, int hwheel) throws IOException { + do { + int sx = clamp(dx); + int sy = clamp(dy); + mouse.input(new byte[] {(byte) buttons, (byte) sx, (byte) sy, (byte) wheel, (byte) hwheel}); + dx -= sx; + dy -= sy; + wheel = 0; + hwheel = 0; + } while (dx != 0 || dy != 0); + } + + private static int clamp(int v) { + return Math.max(-127, Math.min(127, v)); + } + + /** + * Puts the pointer on the entry edge at the given ratio. The mouse is relative and Android + * accelerates it, so: push into the corner, then walk along the edge and measure. + */ + private void enter(int side, float ratio) throws IOException { + Log.i("enter side=" + side + " ratio=" + ratio); + returnSide = Protocol.SIDE_NONE; + boolean horizontalEdge = side == Protocol.SIDE_TOP || side == Protocol.SIDE_BOTTOM; + int intoX = side == Protocol.SIDE_RIGHT ? 1 : -1; + int intoY = side == Protocol.SIDE_BOTTOM ? 1 : -1; + // Into the corner: the entry edge, at the start of the axis along it. + for (int i = 0; i < 40; i++) { + mouse.input(new byte[] {0, (byte) (horizontalEdge ? -127 : 127 * intoX), (byte) (horizontalEdge ? 127 * intoY : -127), 0, 0}); + } + sleep(15); + PointerProbe.Sample s = probe.sample(); + if (s != null && s.width > 0) { + float length = horizontalEdge ? s.width - 1 : s.height - 1; + float target = ratio * length; + float gain = 0; + for (int attempt = 0; attempt < 4; attempt++) { + float now = horizontalEdge ? s.x : s.y; + float error = target - now; + if (Math.abs(error) < 12) { + break; + } + int counts = gain > 0 ? Math.round(error / gain) : (int) Math.signum(error) * 60; + walk(horizontalEdge, counts); + sleep(15); + PointerProbe.Sample next = probe.sample(); + if (next == null) { + break; + } + float moved = (horizontalEdge ? next.x : next.y) - now; + if (counts != 0 && moved * counts > 0) { + gain = moved / counts; + } + s = next; + } + Log.i("entered at " + s + ", target " + target); + } + atEdge = true; + edgeRatio = ratio; + pushed = 0; + edgeSent = false; + returnSide = side; + } + + /** Moves along one axis in small steps, so acceleration stays predictable. */ + private void walk(boolean horizontal, int counts) throws IOException { + int step = counts > 0 ? 10 : -10; + while (counts != 0) { + int s = Math.abs(counts) < Math.abs(step) ? counts : step; + mouse.input(new byte[] {0, (byte) (horizontal ? s : 0), (byte) (horizontal ? 0 : s), 0, 0}); + counts -= s; + sleep(2); + } + } + + /** Watches for the pointer being pushed against the edge that leads back to the Mac. */ + private void onMotion(int dx, int dy) throws IOException { + int side = returnSide; + if (side == Protocol.SIDE_NONE || buttons != 0) { + return; + } + int toward; + switch (side) { + case Protocol.SIDE_LEFT: toward = -dx; break; + case Protocol.SIDE_RIGHT: toward = dx; break; + case Protocol.SIDE_TOP: toward = -dy; break; + default: toward = dy; break; + } + if (toward <= 0) { + if (dx != 0 || dy != 0) { + atEdge = false; + pushed = 0; + } + return; + } + if (atEdge) { + pushed += toward; + if (pushed >= PUSH_TO_LEAVE && !edgeSent) { + edgeSent = true; + int r = Math.round(Math.max(0, Math.min(1, edgeRatio)) * 65535); + send(Protocol.EDGE, new byte[] {(byte) side, (byte) (r >> 8), (byte) r}); + Log.i("edge reached, back to the Mac (ratio " + edgeRatio + ")"); + } + // Keep confirming while pushing: the pointer may have slid along the edge. + } + long now = System.currentTimeMillis(); + if (!probing && now - lastProbe >= PROBE_INTERVAL_MS) { + probing = true; + lastProbe = now; + prober.execute(() -> { + try { + PointerProbe.Sample s = probe.sample(); + if (s != null && s.width > 0) { + boolean touching; + float ratio; + switch (side) { + case Protocol.SIDE_LEFT: touching = s.x <= EDGE_SLOP; ratio = s.y / (s.height - 1); break; + case Protocol.SIDE_RIGHT: touching = s.x >= s.width - 1 - EDGE_SLOP; ratio = s.y / (s.height - 1); break; + case Protocol.SIDE_TOP: touching = s.y <= EDGE_SLOP; ratio = s.x / (s.width - 1); break; + default: touching = s.y >= s.height - 1 - EDGE_SLOP; ratio = s.x / (s.width - 1); break; + } + edgeRatio = ratio; + atEdge = touching; + } + } catch (IOException e) { + Log.w("probe: " + e.getMessage()); + } finally { + probing = false; + } + }); + } + } + + private void setDeviceClipboard(String text) { + if (clipboard == null) { + return; + } + synchronized (clipLock) { + lastClipFromMac = text; + } + clipboard.setText(text); + } + + private void onDeviceClipboard() { + CharSequence cs = clipboard.getText(); + if (cs == null) { + return; + } + String text = cs.toString(); + synchronized (clipLock) { + if (text.equals(lastClipFromMac) || text.equals(lastClipToMac)) { + return; + } + lastClipToMac = text; + } + try { + send(Protocol.DEVICE_CLIPBOARD, text.getBytes(StandardCharsets.UTF_8)); + } catch (IOException e) { + Log.w("clipboard send: " + e.getMessage()); + } + } + + private void sendDevice(int width, int height) throws IOException { + byte[] model = (Build.MANUFACTURER + " " + Build.MODEL).getBytes(StandardCharsets.UTF_8); + byte[] p = new byte[6 + model.length]; + p[0] = (byte) (Protocol.VERSION >> 8); + p[1] = (byte) Protocol.VERSION; + p[2] = (byte) (width >> 8); + p[3] = (byte) width; + p[4] = (byte) (height >> 8); + p[5] = (byte) height; + System.arraycopy(model, 0, p, 6, model.length); + send(Protocol.DEVICE, p); + } + + private synchronized void send(int type, byte[] payload) throws IOException { + out.writeByte(type); + out.writeInt(payload.length); + out.write(payload); + out.flush(); + } + + private int[] displaySize() { + android.util.DisplayMetrics m = new android.util.DisplayMetrics(); + try { + android.view.Display d = ((android.hardware.display.DisplayManager) com.genymobile.scrcpy.FakeContext.get() + .getSystemService(android.content.Context.DISPLAY_SERVICE)).getDisplay(0); + d.getRealMetrics(m); + return new int[] {m.widthPixels, m.heightPixels}; + } catch (RuntimeException e) { + Log.w("display size: " + e.getMessage()); + return new int[] {0, 0}; + } + } + + private static void sleep(long ms) { + try { + Thread.sleep(ms); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } +} diff --git a/server/src/dev/droidbridge/Uhid.java b/server/src/dev/droidbridge/Uhid.java new file mode 100644 index 0000000..a3a5908 --- /dev/null +++ b/server/src/dev/droidbridge/Uhid.java @@ -0,0 +1,94 @@ +package dev.droidbridge; + +import android.system.ErrnoException; +import android.system.Os; +import android.system.OsConstants; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; + +/** A virtual HID device backed by /dev/uhid (Linux Documentation/hid/uhid.rst). */ +final class Uhid { + private static final int UHID_CREATE2 = 11; + private static final int UHID_INPUT2 = 12; + private static final short BUS_VIRTUAL = 0x06; + + static final byte[] MOUSE_DESCRIPTOR = { + 0x05, 0x01, 0x09, 0x02, (byte) 0xA1, 0x01, 0x09, 0x01, (byte) 0xA1, 0x00, + // 5 buttons + 0x05, 0x09, 0x19, 0x01, 0x29, 0x05, 0x15, 0x00, 0x25, 0x01, (byte) 0x95, 0x05, 0x75, 0x01, (byte) 0x81, 0x02, + (byte) 0x95, 0x01, 0x75, 0x03, (byte) 0x81, 0x01, + // X, Y, wheel: relative, -127..127 + 0x05, 0x01, 0x09, 0x30, 0x09, 0x31, 0x09, 0x38, 0x15, (byte) 0x81, 0x25, 0x7F, 0x75, 0x08, (byte) 0x95, 0x03, + (byte) 0x81, 0x06, + // AC Pan (horizontal wheel) + 0x05, 0x0C, 0x0A, 0x38, 0x02, 0x15, (byte) 0x81, 0x25, 0x7F, 0x75, 0x08, (byte) 0x95, 0x01, (byte) 0x81, 0x06, + (byte) 0xC0, (byte) 0xC0, + }; + + static final byte[] KEYBOARD_DESCRIPTOR = { + 0x05, 0x01, 0x09, 0x06, (byte) 0xA1, 0x01, + // modifiers + 0x05, 0x07, 0x19, (byte) 0xE0, 0x29, (byte) 0xE7, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, (byte) 0x95, 0x08, + (byte) 0x81, 0x02, + // reserved byte + 0x75, 0x08, (byte) 0x95, 0x01, (byte) 0x81, 0x01, + // LEDs (output, ignored) + 0x05, 0x08, 0x19, 0x01, 0x29, 0x05, 0x75, 0x01, (byte) 0x95, 0x05, (byte) 0x91, 0x02, 0x75, 0x03, (byte) 0x95, + 0x01, (byte) 0x91, 0x01, + // 6 keys + 0x05, 0x07, 0x19, 0x00, 0x29, 0x65, 0x15, 0x00, 0x25, 0x65, 0x75, 0x08, (byte) 0x95, 0x06, (byte) 0x81, 0x00, + (byte) 0xC0, + }; + + private final FileDescriptor fd; + + Uhid(String name, int vendorId, int productId, byte[] descriptor) throws IOException { + try { + fd = Os.open("/dev/uhid", OsConstants.O_RDWR, 0); + byte[] nameBytes = name.getBytes(StandardCharsets.UTF_8); + ByteBuffer buf = ByteBuffer.allocate(280 + descriptor.length).order(ByteOrder.nativeOrder()); + buf.putInt(UHID_CREATE2); + buf.put(nameBytes, 0, Math.min(nameBytes.length, 127)); + buf.position(4 + 256); + buf.putShort((short) descriptor.length); + buf.putShort(BUS_VIRTUAL); + buf.putInt(vendorId); + buf.putInt(productId); + buf.putInt(0); // version + buf.putInt(0); // country + buf.put(descriptor); + write(buf.array()); + } catch (ErrnoException e) { + throw new IOException("cannot open /dev/uhid", e); + } + } + + void input(byte[] report) throws IOException { + ByteBuffer buf = ByteBuffer.allocate(6 + report.length).order(ByteOrder.nativeOrder()); + buf.putInt(UHID_INPUT2); + buf.putShort((short) report.length); + buf.put(report); + write(buf.array()); + } + + private void write(byte[] data) throws IOException { + try { + Os.write(fd, data, 0, data.length); + } catch (ErrnoException e) { + throw new IOException(e); + } + } + + /** Closing the fd destroys the device. */ + void close() { + try { + Os.close(fd); + } catch (ErrnoException e) { + Log.w("uhid close: " + e.getMessage()); + } + } +} diff --git a/server/third_party/SCRCPY-LICENSE b/server/third_party/SCRCPY-LICENSE new file mode 100644 index 0000000..3325e9f --- /dev/null +++ b/server/third_party/SCRCPY-LICENSE @@ -0,0 +1,204 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright (C) 2018 Genymobile + Copyright (C) 2018-2026 Romain Vimont + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/server/third_party/scrcpy-aidl/android/content/IOnPrimaryClipChangedListener.aidl b/server/third_party/scrcpy-aidl/android/content/IOnPrimaryClipChangedListener.aidl new file mode 100644 index 0000000..46d7f7c --- /dev/null +++ b/server/third_party/scrcpy-aidl/android/content/IOnPrimaryClipChangedListener.aidl @@ -0,0 +1,24 @@ +/** + * Copyright (c) 2008, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content; + +/** + * {@hide} + */ +oneway interface IOnPrimaryClipChangedListener { + void dispatchPrimaryClipChanged(); +} diff --git a/server/third_party/scrcpy-aidl/android/view/IDisplayWindowListener.aidl b/server/third_party/scrcpy-aidl/android/view/IDisplayWindowListener.aidl new file mode 100644 index 0000000..0d1f197 --- /dev/null +++ b/server/third_party/scrcpy-aidl/android/view/IDisplayWindowListener.aidl @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import android.graphics.Rect; +import android.content.res.Configuration; + +import java.util.List; + +/** + * Interface to listen for changes to display window-containers. + * + * This differs from DisplayManager's DisplayListener in a couple ways: + * - onDisplayAdded is always called after the display is actually added to the WM hierarchy. + * This corresponds to the DisplayContent and not the raw Dislay from DisplayManager. + * - onDisplayConfigurationChanged is called for all configuration changes, not just changes + * to displayinfo (eg. windowing-mode). + * + */ +oneway interface IDisplayWindowListener { + + /** + * Called when a new display is added to the WM hierarchy. The existing display ids are returned + * when this listener is registered with WM via {@link #registerDisplayWindowListener}. + */ + void onDisplayAdded(int displayId); + + /** + * Called when a display's window-container configuration has changed. + */ + void onDisplayConfigurationChanged(int displayId, in Configuration newConfig); + + /** + * Called when a display is removed from the hierarchy. + */ + void onDisplayRemoved(int displayId); +} diff --git a/server/third_party/scrcpy-java/android/content/IContentProvider.java b/server/third_party/scrcpy-java/android/content/IContentProvider.java new file mode 100644 index 0000000..bb907dd --- /dev/null +++ b/server/third_party/scrcpy-java/android/content/IContentProvider.java @@ -0,0 +1,5 @@ +package android.content; + +public interface IContentProvider { + // android.content.IContentProvider is hidden, this is a fake one to expose the type to the project +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/AndroidVersions.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/AndroidVersions.java new file mode 100644 index 0000000..5303924 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/AndroidVersions.java @@ -0,0 +1,32 @@ +package com.genymobile.scrcpy; + +import android.os.Build; + +/** + * Android version code constants, done right. + *

+ * API levels + */ +public final class AndroidVersions { + + private AndroidVersions() { + // not instantiable + } + + public static final int API_21_ANDROID_5_0 = Build.VERSION_CODES.LOLLIPOP; + public static final int API_22_ANDROID_5_1 = Build.VERSION_CODES.LOLLIPOP_MR1; + public static final int API_23_ANDROID_6_0 = Build.VERSION_CODES.M; + public static final int API_24_ANDROID_7_0 = Build.VERSION_CODES.N; + public static final int API_25_ANDROID_7_1 = Build.VERSION_CODES.N_MR1; + public static final int API_26_ANDROID_8_0 = Build.VERSION_CODES.O; + public static final int API_27_ANDROID_8_1 = Build.VERSION_CODES.O_MR1; + public static final int API_28_ANDROID_9 = Build.VERSION_CODES.P; + public static final int API_29_ANDROID_10 = Build.VERSION_CODES.Q; + public static final int API_30_ANDROID_11 = Build.VERSION_CODES.R; + public static final int API_31_ANDROID_12 = Build.VERSION_CODES.S; + public static final int API_32_ANDROID_12L = Build.VERSION_CODES.S_V2; + public static final int API_33_ANDROID_13 = Build.VERSION_CODES.TIRAMISU; + public static final int API_34_ANDROID_14 = Build.VERSION_CODES.UPSIDE_DOWN_CAKE; + public static final int API_35_ANDROID_15 = Build.VERSION_CODES.VANILLA_ICE_CREAM; + +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/AsyncProcessor.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/AsyncProcessor.java new file mode 100644 index 0000000..d5da6a9 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/AsyncProcessor.java @@ -0,0 +1,18 @@ +package com.genymobile.scrcpy; + +public interface AsyncProcessor { + interface TerminationListener { + /** + * Notify processor termination + * + * @param fatalError {@code true} if this must cause the termination of the whole scrcpy-server. + */ + void onTerminated(boolean fatalError); + } + + void start(TerminationListener listener); + + void stop(); + + void join() throws InterruptedException; +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/CleanUp.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/CleanUp.java new file mode 100644 index 0000000..a5816c3 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/CleanUp.java @@ -0,0 +1,271 @@ +package com.genymobile.scrcpy; + +import com.genymobile.scrcpy.device.Device; +import com.genymobile.scrcpy.util.Ln; +import com.genymobile.scrcpy.util.Settings; +import com.genymobile.scrcpy.util.SettingsException; +import com.genymobile.scrcpy.wrappers.ServiceManager; + +import android.os.BatteryManager; +import android.os.Looper; +import android.system.ErrnoException; +import android.system.Os; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; + +/** + * Handle the cleanup of scrcpy, even if the main process is killed. + *

+ * This is useful to restore some state when scrcpy is closed, even on device disconnection (which kills the scrcpy process). + */ +public final class CleanUp { + + // Dynamic options + private static final int PENDING_CHANGE_DISPLAY_POWER = 1 << 0; + private int pendingChanges; + private boolean pendingRestoreDisplayPower; + + private Thread thread; + private boolean interrupted; + + private CleanUp(Options options) { + thread = new Thread(() -> runCleanUp(options), "cleanup"); + thread.start(); + } + + public static CleanUp start(Options options) { + return new CleanUp(options); + } + + public synchronized void interrupt() { + // Do not use thread.interrupt() because only the wait() call must be interrupted, not Command.exec() + interrupted = true; + notify(); + } + + public void join() throws InterruptedException { + thread.join(); + } + + private void runCleanUp(Options options) { + boolean disableShowTouches = false; + if (options.getShowTouches()) { + try { + String oldValue = Settings.getAndPutValue(Settings.TABLE_SYSTEM, "show_touches", "1"); + // If "show touches" was disabled, it must be disabled back on clean up + disableShowTouches = !"1".equals(oldValue); + } catch (SettingsException e) { + Ln.e("Could not change \"show_touches\"", e); + } + } + + int restoreStayOn = -1; + if (options.getStayAwake()) { + int stayOn = BatteryManager.BATTERY_PLUGGED_AC | BatteryManager.BATTERY_PLUGGED_USB | BatteryManager.BATTERY_PLUGGED_WIRELESS; + try { + String oldValue = Settings.getAndPutValue(Settings.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(stayOn)); + try { + int currentStayOn = Integer.parseInt(oldValue); + // Restore only if the current value is different + if (currentStayOn != stayOn) { + restoreStayOn = currentStayOn; + } + } catch (NumberFormatException e) { + // ignore + } + } catch (SettingsException e) { + Ln.e("Could not change \"stay_on_while_plugged_in\"", e); + } + } + + int restoreScreenOffTimeout = -1; + int screenOffTimeout = options.getScreenOffTimeout(); + if (screenOffTimeout != -1) { + try { + String oldValue = Settings.getAndPutValue(Settings.TABLE_SYSTEM, "screen_off_timeout", String.valueOf(screenOffTimeout)); + try { + int currentScreenOffTimeout = Integer.parseInt(oldValue); + // Restore only if the current value is different + if (currentScreenOffTimeout != screenOffTimeout) { + restoreScreenOffTimeout = currentScreenOffTimeout; + } + } catch (NumberFormatException e) { + // ignore + } + } catch (SettingsException e) { + Ln.e("Could not change \"screen_off_timeout\"", e); + } + } + + int displayId = options.getDisplayId(); + + int restoreDisplayImePolicy = -1; + if (displayId > 0) { + int displayImePolicy = options.getDisplayImePolicy(); + if (displayImePolicy != -1) { + int currentDisplayImePolicy = ServiceManager.getWindowManager().getDisplayImePolicy(displayId); + if (currentDisplayImePolicy != displayImePolicy) { + ServiceManager.getWindowManager().setDisplayImePolicy(displayId, displayImePolicy); + restoreDisplayImePolicy = currentDisplayImePolicy; + } + } + } + + boolean powerOffScreen = options.getPowerOffScreenOnClose(); + + try { + run(displayId, restoreStayOn, disableShowTouches, powerOffScreen, restoreScreenOffTimeout, restoreDisplayImePolicy); + } catch (IOException e) { + Ln.e("Clean up I/O exception", e); + } + } + + private void run(int displayId, int restoreStayOn, boolean disableShowTouches, boolean powerOffScreen, int restoreScreenOffTimeout, + int restoreDisplayImePolicy) throws IOException { + String[] cmd = { + "app_process", + "/", + CleanUp.class.getName(), + String.valueOf(displayId), + String.valueOf(restoreStayOn), + String.valueOf(disableShowTouches), + String.valueOf(powerOffScreen), + String.valueOf(restoreScreenOffTimeout), + String.valueOf(restoreDisplayImePolicy), + }; + + ProcessBuilder builder = new ProcessBuilder(cmd); + builder.environment().put("CLASSPATH", Server.SERVER_PATH); + Process process = builder.start(); + OutputStream out = process.getOutputStream(); + + while (true) { + int localPendingChanges; + boolean localPendingRestoreDisplayPower; + synchronized (this) { + while (!interrupted && pendingChanges == 0) { + try { + wait(); + } catch (InterruptedException e) { + throw new AssertionError("Clean up thread MUST NOT be interrupted"); + } + } + if (interrupted) { + break; + } + localPendingChanges = pendingChanges; + localPendingRestoreDisplayPower = pendingRestoreDisplayPower; + pendingChanges = 0; + } + if ((localPendingChanges & PENDING_CHANGE_DISPLAY_POWER) != 0) { + out.write(localPendingRestoreDisplayPower ? 1 : 0); + out.flush(); + } + } + } + + public synchronized void setRestoreDisplayPower(boolean restoreDisplayPower) { + pendingRestoreDisplayPower = restoreDisplayPower; + pendingChanges |= PENDING_CHANGE_DISPLAY_POWER; + notify(); + } + + public static void unlinkSelf() { + try { + new File(Server.SERVER_PATH).delete(); + } catch (Exception e) { + Ln.e("Could not unlink server", e); + } + } + + @SuppressWarnings("deprecation") + private static void prepareMainLooper() { + Looper.prepareMainLooper(); + } + + public static void main(String... args) { + try { + // Start a new session to avoid being terminated along with the server process on some devices + Os.setsid(); + } catch (ErrnoException e) { + Ln.e("setsid() failed", e); + } + unlinkSelf(); + + // Needed for workarounds + prepareMainLooper(); + Workarounds.apply(); + + int displayId = Integer.parseInt(args[0]); + int restoreStayOn = Integer.parseInt(args[1]); + boolean disableShowTouches = Boolean.parseBoolean(args[2]); + boolean powerOffScreen = Boolean.parseBoolean(args[3]); + int restoreScreenOffTimeout = Integer.parseInt(args[4]); + int restoreDisplayImePolicy = Integer.parseInt(args[5]); + + // Dynamic option + boolean restoreDisplayPower = false; + + try { + // Wait for the server to die + int msg; + while ((msg = System.in.read()) != -1) { + // Only restore display power + assert msg == 0 || msg == 1; + restoreDisplayPower = msg != 0; + } + } catch (IOException e) { + // Expected when the server is dead + } + + Ln.i("Cleaning up"); + + if (disableShowTouches) { + Ln.i("Disabling \"show touches\""); + try { + Settings.putValue(Settings.TABLE_SYSTEM, "show_touches", "0"); + } catch (SettingsException e) { + Ln.e("Could not restore \"show_touches\"", e); + } + } + + if (restoreStayOn != -1) { + Ln.i("Restoring \"stay awake\""); + try { + Settings.putValue(Settings.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(restoreStayOn)); + } catch (SettingsException e) { + Ln.e("Could not restore \"stay_on_while_plugged_in\"", e); + } + } + + if (restoreScreenOffTimeout != -1) { + Ln.i("Restoring \"screen off timeout\""); + try { + Settings.putValue(Settings.TABLE_SYSTEM, "screen_off_timeout", String.valueOf(restoreScreenOffTimeout)); + } catch (SettingsException e) { + Ln.e("Could not restore \"screen_off_timeout\"", e); + } + } + + if (restoreDisplayImePolicy != -1) { + Ln.i("Restoring \"display IME policy\""); + ServiceManager.getWindowManager().setDisplayImePolicy(displayId, restoreDisplayImePolicy); + } + + // Change the power of the main display when mirroring a virtual display + int targetDisplayId = displayId != Device.DISPLAY_ID_NONE ? displayId : 0; + if (Device.isScreenOn(targetDisplayId)) { + if (powerOffScreen) { + Ln.i("Power off screen"); + Device.powerOffScreen(targetDisplayId); + } else if (restoreDisplayPower) { + Ln.i("Restoring display power"); + Device.setDisplayPower(targetDisplayId, true); + } + } + + System.exit(0); + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/FakeContext.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/FakeContext.java new file mode 100644 index 0000000..5d41a8f --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/FakeContext.java @@ -0,0 +1,127 @@ +package com.genymobile.scrcpy; + +import com.genymobile.scrcpy.wrappers.ServiceManager; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.content.AttributionSource; +import android.content.ContentResolver; +import android.content.Context; +import android.content.ContextWrapper; +import android.content.IContentProvider; +import android.os.Binder; +import android.os.Process; + +import java.lang.reflect.Field; + +public final class FakeContext extends ContextWrapper { + + public static final String PACKAGE_NAME = "com.android.shell"; + public static final int ROOT_UID = 0; // Like android.os.Process.ROOT_UID, but before API 29 + + private static final FakeContext INSTANCE = new FakeContext(); + + public static FakeContext get() { + return INSTANCE; + } + + private final ContentResolver contentResolver = new ContentResolver(this) { + @SuppressWarnings({"unused", "ProtectedMemberInFinalClass"}) + // @Override (but super-class method not visible) + protected IContentProvider acquireProvider(Context c, String name) { + return ServiceManager.getActivityManager().getContentProviderExternal(name, new Binder()); + } + + @SuppressWarnings("unused") + // @Override (but super-class method not visible) + public boolean releaseProvider(IContentProvider icp) { + return false; + } + + @SuppressWarnings({"unused", "ProtectedMemberInFinalClass"}) + // @Override (but super-class method not visible) + protected IContentProvider acquireUnstableProvider(Context c, String name) { + return null; + } + + @SuppressWarnings("unused") + // @Override (but super-class method not visible) + public boolean releaseUnstableProvider(IContentProvider icp) { + return false; + } + + @SuppressWarnings("unused") + // @Override (but super-class method not visible) + public void unstableProviderDied(IContentProvider icp) { + // ignore + } + }; + + private FakeContext() { + super(Workarounds.getSystemContext()); + } + + @Override + public String getPackageName() { + return PACKAGE_NAME; + } + + @Override + public String getOpPackageName() { + return PACKAGE_NAME; + } + + @TargetApi(AndroidVersions.API_31_ANDROID_12) + @Override + public AttributionSource getAttributionSource() { + AttributionSource.Builder builder = new AttributionSource.Builder(Process.SHELL_UID); + builder.setPackageName(PACKAGE_NAME); + return builder.build(); + } + + // @Override to be added on SDK upgrade for Android 14 + @SuppressWarnings("unused") + public int getDeviceId() { + return 0; + } + + @Override + public Context getApplicationContext() { + return this; + } + + @Override + public Context createPackageContext(String packageName, int flags) { + return this; + } + + @Override + public ContentResolver getContentResolver() { + return contentResolver; + } + + @SuppressLint("SoonBlockedPrivateApi") + @Override + public Object getSystemService(String name) { + Object service = super.getSystemService(name); + if (service == null) { + return null; + } + + // "semclipboard" is a Samsung-internal service + // See: + // - + // - + if (Context.CLIPBOARD_SERVICE.equals(name) || "semclipboard".equals(name) || Context.ACTIVITY_SERVICE.equals(name)) { + try { + Field field = service.getClass().getDeclaredField("mContext"); + field.setAccessible(true); + field.set(service, this); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + return service; + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/Options.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/Options.java new file mode 100644 index 0000000..26a6ba9 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/Options.java @@ -0,0 +1,707 @@ +package com.genymobile.scrcpy; + +import com.genymobile.scrcpy.audio.AudioCodec; +import com.genymobile.scrcpy.audio.AudioSource; +import com.genymobile.scrcpy.device.Device; +import com.genymobile.scrcpy.model.CodecOption; +import com.genymobile.scrcpy.model.NewDisplay; +import com.genymobile.scrcpy.model.Orientation; +import com.genymobile.scrcpy.model.Size; +import com.genymobile.scrcpy.util.Ln; +import com.genymobile.scrcpy.video.CameraAspectRatio; +import com.genymobile.scrcpy.video.CameraFacing; +import com.genymobile.scrcpy.video.VideoCodec; +import com.genymobile.scrcpy.video.VideoSource; +import com.genymobile.scrcpy.wrappers.WindowManager; + +import android.graphics.Rect; +import android.util.Pair; + +import java.util.List; +import java.util.Locale; + +public class Options { + + private Ln.Level logLevel = Ln.Level.DEBUG; + private int scid = -1; // 31-bit non-negative value, or -1 + private boolean video = true; + private boolean audio = true; + private int maxSize; + private int minSizeAlignment = 1; + private VideoCodec videoCodec = VideoCodec.H264; + private AudioCodec audioCodec = AudioCodec.OPUS; + private VideoSource videoSource = VideoSource.DISPLAY; + private AudioSource audioSource = AudioSource.OUTPUT; + private boolean audioDup; + private int videoBitRate = 8000000; + private int audioBitRate = 128000; + private float maxFps; + private float angle; + private boolean tunnelForward; + private Rect crop; + private boolean control = true; + private int displayId; + private String cameraId; + private Size cameraSize; + private CameraFacing cameraFacing; + private CameraAspectRatio cameraAspectRatio; + private float cameraZoom = 1; + private int cameraFps; + private boolean cameraHighSpeed; + private boolean cameraTorch; + private boolean showTouches; + private boolean stayAwake; + private int screenOffTimeout = -1; + private int displayImePolicy = -1; + private List videoCodecOptions; + private List audioCodecOptions; + + private String videoEncoder; + private String audioEncoder; + private boolean powerOffScreenOnClose; + private boolean clipboardAutosync = true; + private boolean downsizeOnError = true; + private boolean cleanup = true; + private boolean powerOn = true; + + private NewDisplay newDisplay; + private boolean vdDestroyContent = true; + private boolean vdSystemDecorations = true; + private boolean flexDisplay; + + private boolean keepActive; + private boolean ignoreVideoEncoderConstraints; + + private Orientation.Lock captureOrientationLock = Orientation.Lock.Unlocked; + private Orientation captureOrientation = Orientation.Orient0; + + private boolean listEncoders; + private boolean listDisplays; + private boolean listCameras; + private boolean listCameraSizes; + private boolean listApps; + + // Options not used by the scrcpy client, but useful to use scrcpy-server directly + private boolean sendDeviceMeta = true; // send device name and size + private boolean sendFrameMeta = true; // send PTS so that the client may record properly + private boolean sendDummyByte = true; // write a byte on start to detect connection issues + private boolean sendStreamMeta = true; // write the stream metadata (codec and session) + + public Ln.Level getLogLevel() { + return logLevel; + } + + public int getScid() { + return scid; + } + + public boolean getVideo() { + return video; + } + + public boolean getAudio() { + return audio; + } + + public int getMaxSize() { + return maxSize; + } + + public int getMinSizeAlignment() { + return minSizeAlignment; + } + + public VideoCodec getVideoCodec() { + return videoCodec; + } + + public AudioCodec getAudioCodec() { + return audioCodec; + } + + public VideoSource getVideoSource() { + return videoSource; + } + + public AudioSource getAudioSource() { + return audioSource; + } + + public boolean getAudioDup() { + return audioDup; + } + + public int getVideoBitRate() { + return videoBitRate; + } + + public int getAudioBitRate() { + return audioBitRate; + } + + public float getMaxFps() { + return maxFps; + } + + public float getAngle() { + return angle; + } + + public boolean isTunnelForward() { + return tunnelForward; + } + + public Rect getCrop() { + return crop; + } + + public boolean getControl() { + return control; + } + + public int getDisplayId() { + return displayId; + } + + public String getCameraId() { + return cameraId; + } + + public Size getCameraSize() { + return cameraSize; + } + + public CameraFacing getCameraFacing() { + return cameraFacing; + } + + public CameraAspectRatio getCameraAspectRatio() { + return cameraAspectRatio; + } + + public float getCameraZoom() { + return cameraZoom; + } + + public int getCameraFps() { + return cameraFps; + } + + public boolean getCameraHighSpeed() { + return cameraHighSpeed; + } + + public boolean getCameraTorch() { + return cameraTorch; + } + + public boolean getShowTouches() { + return showTouches; + } + + public boolean getStayAwake() { + return stayAwake; + } + + public int getScreenOffTimeout() { + return screenOffTimeout; + } + + public int getDisplayImePolicy() { + return displayImePolicy; + } + + public List getVideoCodecOptions() { + return videoCodecOptions; + } + + public List getAudioCodecOptions() { + return audioCodecOptions; + } + + public String getVideoEncoder() { + return videoEncoder; + } + + public String getAudioEncoder() { + return audioEncoder; + } + + public boolean getPowerOffScreenOnClose() { + return this.powerOffScreenOnClose; + } + + public boolean getClipboardAutosync() { + return clipboardAutosync; + } + + public boolean getDownsizeOnError() { + return downsizeOnError; + } + + public boolean getCleanup() { + return cleanup; + } + + public boolean getPowerOn() { + return powerOn; + } + + public NewDisplay getNewDisplay() { + return newDisplay; + } + + public Orientation getCaptureOrientation() { + return captureOrientation; + } + + public Orientation.Lock getCaptureOrientationLock() { + return captureOrientationLock; + } + + public boolean getVDDestroyContent() { + return vdDestroyContent; + } + + public boolean getVDSystemDecorations() { + return vdSystemDecorations; + } + + public boolean getKeepActive() { + return keepActive; + } + + public boolean getFlexDisplay() { + return flexDisplay; + } + + public boolean getIgnoreVideoEncoderConstraints() { + return ignoreVideoEncoderConstraints; + } + + public boolean getList() { + return listEncoders || listDisplays || listCameras || listCameraSizes || listApps; + } + + public boolean getListEncoders() { + return listEncoders; + } + + public boolean getListDisplays() { + return listDisplays; + } + + public boolean getListCameras() { + return listCameras; + } + + public boolean getListCameraSizes() { + return listCameraSizes; + } + + public boolean getListApps() { + return listApps; + } + + public boolean getSendDeviceMeta() { + return sendDeviceMeta; + } + + public boolean getSendFrameMeta() { + return sendFrameMeta; + } + + public boolean getSendDummyByte() { + return sendDummyByte; + } + + public boolean getSendStreamMeta() { + return sendStreamMeta; + } + + @SuppressWarnings("MethodLength") + public static Options parse(String... args) { + if (args.length < 1) { + throw new IllegalArgumentException("Missing client version"); + } + + String clientVersion = args[0]; + if (!clientVersion.equals(BuildConfig.VERSION_NAME)) { + throw new IllegalArgumentException( + "The server version (" + BuildConfig.VERSION_NAME + ") does not match the client " + "(" + clientVersion + ")"); + } + + Options options = new Options(); + + for (int i = 1; i < args.length; ++i) { + String arg = args[i]; + int equalIndex = arg.indexOf('='); + if (equalIndex == -1) { + throw new IllegalArgumentException("Invalid key=value pair: \"" + arg + "\""); + } + String key = arg.substring(0, equalIndex); + String value = arg.substring(equalIndex + 1); + switch (key) { + case "scid": + int scid = Integer.parseInt(value, 0x10); + if (scid < -1) { + throw new IllegalArgumentException("scid may not be negative (except -1 for 'none'): " + scid); + } + options.scid = scid; + break; + case "log_level": + options.logLevel = Ln.Level.valueOf(value.toUpperCase(Locale.ENGLISH)); + break; + case "video": + options.video = Boolean.parseBoolean(value); + break; + case "audio": + options.audio = Boolean.parseBoolean(value); + break; + case "video_codec": + VideoCodec videoCodec = VideoCodec.findByName(value); + if (videoCodec == null) { + throw new IllegalArgumentException("Video codec " + value + " not supported"); + } + options.videoCodec = videoCodec; + break; + case "audio_codec": + AudioCodec audioCodec = AudioCodec.findByName(value); + if (audioCodec == null) { + throw new IllegalArgumentException("Audio codec " + value + " not supported"); + } + options.audioCodec = audioCodec; + break; + case "video_source": + VideoSource videoSource = VideoSource.findByName(value); + if (videoSource == null) { + throw new IllegalArgumentException("Video source " + value + " not supported"); + } + options.videoSource = videoSource; + break; + case "audio_source": + AudioSource audioSource = AudioSource.findByName(value); + if (audioSource == null) { + throw new IllegalArgumentException("Audio source " + value + " not supported"); + } + options.audioSource = audioSource; + break; + case "audio_dup": + options.audioDup = Boolean.parseBoolean(value); + break; + case "max_size": + options.maxSize = Integer.parseInt(value); + break; + case "min_size_alignment": + int align = Integer.parseInt(value); + if (align < 1 || align > 16 || (align & (align - 1)) != 0) { + throw new IllegalArgumentException("min_size_alignment (" + align + ") must be 1, 2, 4, 8 or 16"); + } + options.minSizeAlignment = align; + break; + case "video_bit_rate": + options.videoBitRate = Integer.parseInt(value); + break; + case "audio_bit_rate": + options.audioBitRate = Integer.parseInt(value); + break; + case "max_fps": + options.maxFps = parseFloat("max_fps", value); + break; + case "angle": + options.angle = parseFloat("angle", value); + break; + case "tunnel_forward": + options.tunnelForward = Boolean.parseBoolean(value); + break; + case "crop": + if (!value.isEmpty()) { + options.crop = parseCrop(value); + } + break; + case "control": + options.control = Boolean.parseBoolean(value); + break; + case "display_id": + options.displayId = Integer.parseInt(value); + break; + case "show_touches": + options.showTouches = Boolean.parseBoolean(value); + break; + case "stay_awake": + options.stayAwake = Boolean.parseBoolean(value); + break; + case "screen_off_timeout": + options.screenOffTimeout = Integer.parseInt(value); + if (options.screenOffTimeout < -1) { + throw new IllegalArgumentException("Invalid screen off timeout: " + options.screenOffTimeout); + } + break; + case "video_codec_options": + options.videoCodecOptions = CodecOption.parse(value); + break; + case "audio_codec_options": + options.audioCodecOptions = CodecOption.parse(value); + break; + case "video_encoder": + if (!value.isEmpty()) { + options.videoEncoder = value; + } + break; + case "audio_encoder": + if (!value.isEmpty()) { + options.audioEncoder = value; + } + break; + case "power_off_on_close": + options.powerOffScreenOnClose = Boolean.parseBoolean(value); + break; + case "clipboard_autosync": + options.clipboardAutosync = Boolean.parseBoolean(value); + break; + case "downsize_on_error": + options.downsizeOnError = Boolean.parseBoolean(value); + break; + case "cleanup": + options.cleanup = Boolean.parseBoolean(value); + break; + case "power_on": + options.powerOn = Boolean.parseBoolean(value); + break; + case "list_encoders": + options.listEncoders = Boolean.parseBoolean(value); + break; + case "list_displays": + options.listDisplays = Boolean.parseBoolean(value); + break; + case "list_cameras": + options.listCameras = Boolean.parseBoolean(value); + break; + case "list_camera_sizes": + options.listCameraSizes = Boolean.parseBoolean(value); + break; + case "list_apps": + options.listApps = Boolean.parseBoolean(value); + break; + case "camera_id": + if (!value.isEmpty()) { + options.cameraId = value; + } + break; + case "camera_size": + if (!value.isEmpty()) { + options.cameraSize = parseSize(value); + } + break; + case "camera_facing": + if (!value.isEmpty()) { + CameraFacing facing = CameraFacing.findByName(value); + if (facing == null) { + throw new IllegalArgumentException("Camera facing " + value + " not supported"); + } + options.cameraFacing = facing; + } + break; + case "camera_ar": + if (!value.isEmpty()) { + options.cameraAspectRatio = parseCameraAspectRatio(value); + } + break; + case "camera_zoom": + if (!value.isEmpty()) { + options.cameraZoom = Float.parseFloat(value); + } + break; + case "camera_fps": + options.cameraFps = Integer.parseInt(value); + break; + case "camera_high_speed": + options.cameraHighSpeed = Boolean.parseBoolean(value); + break; + case "camera_torch": + options.cameraTorch = Boolean.parseBoolean(value); + break; + case "new_display": + options.newDisplay = parseNewDisplay(value); + break; + case "vd_destroy_content": + options.vdDestroyContent = Boolean.parseBoolean(value); + break; + case "vd_system_decorations": + options.vdSystemDecorations = Boolean.parseBoolean(value); + break; + case "flex_display": + options.flexDisplay = Boolean.parseBoolean(value); + break; + case "capture_orientation": + Pair pair = parseCaptureOrientation(value); + options.captureOrientationLock = pair.first; + options.captureOrientation = pair.second; + break; + case "display_ime_policy": + options.displayImePolicy = parseDisplayImePolicy(value); + break; + case "keep_active": + options.keepActive = Boolean.parseBoolean(value); + break; + case "ignore_video_encoder_constraints": + options.ignoreVideoEncoderConstraints = Boolean.parseBoolean(value); + break; + case "send_device_meta": + options.sendDeviceMeta = Boolean.parseBoolean(value); + break; + case "send_frame_meta": + options.sendFrameMeta = Boolean.parseBoolean(value); + break; + case "send_dummy_byte": + options.sendDummyByte = Boolean.parseBoolean(value); + break; + case "send_stream_meta": + options.sendStreamMeta = Boolean.parseBoolean(value); + break; + case "raw_stream": + boolean rawStream = Boolean.parseBoolean(value); + if (rawStream) { + options.sendDeviceMeta = false; + options.sendFrameMeta = false; + options.sendDummyByte = false; + options.sendStreamMeta = false; + } + break; + default: + Ln.w("Unknown server option: " + key); + break; + } + } + + if (options.newDisplay != null) { + assert options.displayId == 0 : "Must not set both displayId and newDisplay"; + options.displayId = Device.DISPLAY_ID_NONE; + } + + return options; + } + + private static Rect parseCrop(String crop) { + // input format: "width:height:x:y" + String[] tokens = crop.split(":"); + if (tokens.length != 4) { + throw new IllegalArgumentException("Crop must contains 4 values separated by colons: \"" + crop + "\""); + } + int width = Integer.parseInt(tokens[0]); + int height = Integer.parseInt(tokens[1]); + if (width <= 0 || height <= 0) { + throw new IllegalArgumentException("Invalid crop size: " + width + "x" + height); + } + int x = Integer.parseInt(tokens[2]); + int y = Integer.parseInt(tokens[3]); + if (x < 0 || y < 0) { + throw new IllegalArgumentException("Invalid crop offset: " + x + ":" + y); + } + return new Rect(x, y, x + width, y + height); + } + + private static Size parseSize(String size) { + // input format: "x" + String[] tokens = size.split("x"); + if (tokens.length != 2) { + throw new IllegalArgumentException("Invalid size format (expected x): \"" + size + "\""); + } + int width = Integer.parseInt(tokens[0]); + int height = Integer.parseInt(tokens[1]); + if (width <= 0 || height <= 0) { + throw new IllegalArgumentException("Invalid non-positive size dimension: \"" + size + "\""); + } + return new Size(width, height); + } + + private static CameraAspectRatio parseCameraAspectRatio(String ar) { + if ("sensor".equals(ar)) { + return CameraAspectRatio.sensorAspectRatio(); + } + + String[] tokens = ar.split(":"); + if (tokens.length == 2) { + int w = Integer.parseInt(tokens[0]); + int h = Integer.parseInt(tokens[1]); + return CameraAspectRatio.fromFraction(w, h); + } + + float floatAr = Float.parseFloat(tokens[0]); + return CameraAspectRatio.fromFloat(floatAr); + } + + private static float parseFloat(String key, String value) { + try { + return Float.parseFloat(value); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Invalid float value for " + key + ": \"" + value + "\""); + } + } + + private static NewDisplay parseNewDisplay(String newDisplay) { + // Possible inputs: + // - "" (empty string) + // - "x/" + // - "x" + // - "/" + if (newDisplay.isEmpty()) { + return new NewDisplay(); + } + + String[] tokens = newDisplay.split("/"); + + Size size; + if (!tokens[0].isEmpty()) { + size = parseSize(tokens[0]); + } else { + size = null; + } + + int dpi; + if (tokens.length >= 2) { + dpi = Integer.parseInt(tokens[1]); + if (dpi <= 0) { + throw new IllegalArgumentException("Invalid non-positive dpi: " + tokens[1]); + } + } else { + dpi = 0; + } + + return new NewDisplay(size, dpi); + } + + private static Pair parseCaptureOrientation(String value) { + if (value.isEmpty()) { + throw new IllegalArgumentException("Empty capture orientation string"); + } + + Orientation.Lock lock; + if (value.charAt(0) == '@') { + // Consume '@' + value = value.substring(1); + if (value.isEmpty()) { + // Only '@': lock to the initial orientation (orientation is unused) + return Pair.create(Orientation.Lock.LockedInitial, Orientation.Orient0); + } + lock = Orientation.Lock.LockedValue; + } else { + lock = Orientation.Lock.Unlocked; + } + + return Pair.create(lock, Orientation.getByName(value)); + } + + private static int parseDisplayImePolicy(String value) { + switch (value) { + case "local": + return WindowManager.DISPLAY_IME_POLICY_LOCAL; + case "fallback": + return WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY; + case "hide": + return WindowManager.DISPLAY_IME_POLICY_HIDE; + default: + throw new IllegalArgumentException("Invalid display IME policy: " + value); + } + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/Server.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/Server.java new file mode 100644 index 0000000..d7d1a76 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/Server.java @@ -0,0 +1,291 @@ +package com.genymobile.scrcpy; + +import com.genymobile.scrcpy.audio.AudioCapture; +import com.genymobile.scrcpy.audio.AudioCodec; +import com.genymobile.scrcpy.audio.AudioDirectCapture; +import com.genymobile.scrcpy.audio.AudioEncoder; +import com.genymobile.scrcpy.audio.AudioPlaybackCapture; +import com.genymobile.scrcpy.audio.AudioRawRecorder; +import com.genymobile.scrcpy.audio.AudioSource; +import com.genymobile.scrcpy.control.ControlChannel; +import com.genymobile.scrcpy.control.Controller; +import com.genymobile.scrcpy.device.DesktopConnection; +import com.genymobile.scrcpy.device.Device; +import com.genymobile.scrcpy.device.Streamer; +import com.genymobile.scrcpy.model.ConfigurationException; +import com.genymobile.scrcpy.model.NewDisplay; +import com.genymobile.scrcpy.opengl.OpenGLRunner; +import com.genymobile.scrcpy.util.Ln; +import com.genymobile.scrcpy.util.LogUtils; +import com.genymobile.scrcpy.video.CameraCapture; +import com.genymobile.scrcpy.video.NewDisplayCapture; +import com.genymobile.scrcpy.video.ScreenCapture; +import com.genymobile.scrcpy.video.SurfaceCapture; +import com.genymobile.scrcpy.video.SurfaceEncoder; +import com.genymobile.scrcpy.video.VideoSource; + +import android.annotation.SuppressLint; +import android.os.Build; +import android.os.Looper; +import android.system.Os; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +public final class Server { + + public static final String SERVER_PATH; + + static { + String[] classPaths = System.getProperty("java.class.path").split(File.pathSeparator); + // By convention, scrcpy is always executed with the absolute path of scrcpy-server.jar as the first item in the classpath + SERVER_PATH = classPaths[0]; + } + + private static class Completion { + private int running; + private boolean fatalError; + + Completion(int running) { + this.running = running; + } + + synchronized void addCompleted(boolean fatalError) { + --running; + if (fatalError) { + this.fatalError = true; + } + if (running == 0 || this.fatalError) { + Looper.getMainLooper().quitSafely(); + } + } + } + + private Server() { + // not instantiable + } + + private static void scrcpy(Options options) throws IOException, ConfigurationException { + if (Build.VERSION.SDK_INT < AndroidVersions.API_31_ANDROID_12 && options.getVideoSource() == VideoSource.CAMERA) { + Ln.e("Camera mirroring is not supported before Android 12"); + throw new ConfigurationException("Camera mirroring is not supported"); + } + + if (Build.VERSION.SDK_INT < AndroidVersions.API_29_ANDROID_10) { + if (options.getNewDisplay() != null) { + Ln.e("New virtual display is not supported before Android 10"); + throw new ConfigurationException("New virtual display is not supported"); + } + if (options.getDisplayImePolicy() != -1) { + Ln.e("Display IME policy is not supported before Android 10"); + throw new ConfigurationException("Display IME policy is not supported"); + } + } + + CleanUp cleanUp = null; + + if (options.getCleanup()) { + cleanUp = CleanUp.start(options); + } + + int scid = options.getScid(); + boolean tunnelForward = options.isTunnelForward(); + boolean control = options.getControl(); + boolean video = options.getVideo(); + boolean audio = options.getAudio(); + boolean sendDummyByte = options.getSendDummyByte(); + + Workarounds.apply(); + + List asyncProcessors = new ArrayList<>(); + + DesktopConnection connection = DesktopConnection.open(scid, tunnelForward, video, audio, control, sendDummyByte); + try { + if (options.getSendDeviceMeta()) { + connection.sendDeviceMeta(Device.getDeviceName()); + } + + Controller controller = null; + + if (control) { + ControlChannel controlChannel = connection.getControlChannel(); + controller = new Controller(controlChannel, cleanUp, options); + asyncProcessors.add(controller); + } + + if (audio) { + AudioCodec audioCodec = options.getAudioCodec(); + AudioSource audioSource = options.getAudioSource(); + AudioCapture audioCapture; + if (audioSource.isDirect()) { + audioCapture = new AudioDirectCapture(audioSource); + } else { + audioCapture = new AudioPlaybackCapture(options.getAudioDup()); + } + + Streamer audioStreamer = new Streamer(connection.getAudioFd(), audioCodec, options.getSendStreamMeta(), options.getSendFrameMeta()); + AsyncProcessor audioRecorder; + if (audioCodec == AudioCodec.RAW) { + audioRecorder = new AudioRawRecorder(audioCapture, audioStreamer); + } else { + audioRecorder = new AudioEncoder(audioCapture, audioStreamer, options); + } + asyncProcessors.add(audioRecorder); + } + + if (video) { + Streamer videoStreamer = new Streamer(connection.getVideoFd(), options.getVideoCodec(), options.getSendStreamMeta(), + options.getSendFrameMeta()); + SurfaceCapture surfaceCapture; + if (options.getVideoSource() == VideoSource.DISPLAY) { + NewDisplay newDisplay = options.getNewDisplay(); + if (newDisplay != null) { + surfaceCapture = new NewDisplayCapture(controller, options); + } else { + assert options.getDisplayId() != Device.DISPLAY_ID_NONE; + surfaceCapture = new ScreenCapture(controller, options); + } + } else { + surfaceCapture = new CameraCapture(options); + } + SurfaceEncoder surfaceEncoder = new SurfaceEncoder(surfaceCapture, videoStreamer, options); + asyncProcessors.add(surfaceEncoder); + + if (controller != null) { + controller.setSurfaceCapture(surfaceCapture); + } + } + + Completion completion = new Completion(asyncProcessors.size()); + for (AsyncProcessor asyncProcessor : asyncProcessors) { + asyncProcessor.start((fatalError) -> { + completion.addCompleted(fatalError); + }); + } + + Looper.loop(); // interrupted by the Completion implementation + } finally { + if (cleanUp != null) { + cleanUp.interrupt(); + } + for (AsyncProcessor asyncProcessor : asyncProcessors) { + asyncProcessor.stop(); + } + + connection.shutdown(); + + try { + if (cleanUp != null) { + cleanUp.join(); + } + for (AsyncProcessor asyncProcessor : asyncProcessors) { + asyncProcessor.join(); + } + + OpenGLRunner.shutdown(); + } catch (InterruptedException e) { + // ignore + } + + connection.close(); + } + } + + private static void prepareMainLooper() { + // Like Looper.prepareMainLooper(), but with quitAllowed set to true + Looper.prepare(); + synchronized (Looper.class) { + try { + @SuppressLint("DiscouragedPrivateApi") + Field field = Looper.class.getDeclaredField("sMainLooper"); + field.setAccessible(true); + field.set(null, Looper.myLooper()); + } catch (ReflectiveOperationException e) { + throw new AssertionError(e); + } + } + } + + public static void main(String... args) { + int status = 0; + try { + internalMain(args); + } catch (Throwable t) { + Ln.e(t.getMessage(), t); + status = 1; + } finally { + // By default, the Java process exits when all non-daemon threads are terminated. + // The Android SDK might start some non-daemon threads internally, preventing the scrcpy server to exit. + // So force the process to exit explicitly. + System.exit(status); + } + } + + private static void internalMain(String... args) throws Exception { + Thread.UncaughtExceptionHandler defaultHandler = Thread.getDefaultUncaughtExceptionHandler(); + Thread.setDefaultUncaughtExceptionHandler((t, e) -> { + Ln.e("Exception on thread " + t, e); + if (defaultHandler != null) { + defaultHandler.uncaughtException(t, e); + } + }); + + dropRootPrivileges(); + + prepareMainLooper(); + + Options options = Options.parse(args); + + Ln.disableSystemStreams(); + Ln.initLogLevel(options.getLogLevel()); + + Ln.i("Device: [" + Build.MANUFACTURER + "] " + Build.BRAND + " " + Build.MODEL + " (Android " + Build.VERSION.RELEASE + ")"); + + if (options.getList()) { + if (options.getCleanup()) { + CleanUp.unlinkSelf(); + } + + if (options.getListEncoders()) { + Ln.i(LogUtils.buildVideoEncoderListMessage()); + Ln.i(LogUtils.buildAudioEncoderListMessage()); + } + if (options.getListDisplays()) { + Ln.i(LogUtils.buildDisplayListMessage()); + } + if (options.getListCameras() || options.getListCameraSizes()) { + Workarounds.apply(); + Ln.i(LogUtils.buildCameraListMessage(options.getListCameraSizes())); + } + if (options.getListApps()) { + Workarounds.apply(); + Ln.i("Processing Android apps... (this may take some time)"); + Ln.i(LogUtils.buildAppListMessage()); + } + // Just print the requested data, do not mirror + return; + } + + try { + scrcpy(options); + } catch (ConfigurationException e) { + // Do not print stack trace, a user-friendly error-message has already been logged + } + } + + @SuppressWarnings("deprecation") + private static void dropRootPrivileges() { + try { + if (Os.getuid() == 0) { + // Copy-paste does not work with root user + // + Os.setuid(2000); + } + } catch (Exception e) { + Ln.w("Cannot set UID", e); + } + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/Workarounds.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/Workarounds.java new file mode 100644 index 0000000..1e012de --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/Workarounds.java @@ -0,0 +1,296 @@ +package com.genymobile.scrcpy; + +import com.genymobile.scrcpy.audio.AudioCaptureException; +import com.genymobile.scrcpy.util.Ln; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.app.Application; +import android.app.Instrumentation; +import android.content.AttributionSource; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.media.AudioAttributes; +import android.media.AudioManager; +import android.media.AudioRecord; +import android.os.Build; +import android.os.Looper; +import android.os.Parcel; + +import java.lang.ref.WeakReference; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +@SuppressLint("PrivateApi,BlockedPrivateApi,SoonBlockedPrivateApi,DiscouragedPrivateApi") +public final class Workarounds { + + private static final Class ACTIVITY_THREAD_CLASS; + private static final Object ACTIVITY_THREAD; + + static { + try { + // ActivityThread activityThread = new ActivityThread(); + ACTIVITY_THREAD_CLASS = Class.forName("android.app.ActivityThread"); + Constructor activityThreadConstructor = ACTIVITY_THREAD_CLASS.getDeclaredConstructor(); + activityThreadConstructor.setAccessible(true); + ACTIVITY_THREAD = activityThreadConstructor.newInstance(); + + // ActivityThread.sCurrentActivityThread = activityThread; + Field sCurrentActivityThreadField = ACTIVITY_THREAD_CLASS.getDeclaredField("sCurrentActivityThread"); + sCurrentActivityThreadField.setAccessible(true); + sCurrentActivityThreadField.set(null, ACTIVITY_THREAD); + + // activityThread.mSystemThread = true; + Field mSystemThreadField = ACTIVITY_THREAD_CLASS.getDeclaredField("mSystemThread"); + mSystemThreadField.setAccessible(true); + mSystemThreadField.setBoolean(ACTIVITY_THREAD, true); + } catch (Exception e) { + throw new AssertionError(e); + } + } + + private Workarounds() { + // not instantiable + } + + public static void apply() { + if (Build.VERSION.SDK_INT >= AndroidVersions.API_31_ANDROID_12) { + // On some Samsung devices, DisplayManagerGlobal.getDisplayInfoLocked() calls ActivityThread.currentActivityThread().getConfiguration(), + // which requires a non-null ConfigurationController. + // ConfigurationController was introduced in Android 12, so do not attempt to set it on lower versions. + // + // Must be called before fillAppContext() because it is necessary to get a valid system context. + fillConfigurationController(); + } + + // On ONYX devices, fillAppInfo() breaks video mirroring: + // + boolean mustFillAppInfo = !Build.BRAND.equalsIgnoreCase("ONYX"); + + if (mustFillAppInfo) { + fillAppInfo(); + } + + fillAppContext(); + } + + private static void fillAppInfo() { + try { + // ActivityThread.AppBindData appBindData = new ActivityThread.AppBindData(); + Class appBindDataClass = Class.forName("android.app.ActivityThread$AppBindData"); + Constructor appBindDataConstructor = appBindDataClass.getDeclaredConstructor(); + appBindDataConstructor.setAccessible(true); + Object appBindData = appBindDataConstructor.newInstance(); + + ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.packageName = FakeContext.PACKAGE_NAME; + + // appBindData.appInfo = applicationInfo; + Field appInfoField = appBindDataClass.getDeclaredField("appInfo"); + appInfoField.setAccessible(true); + appInfoField.set(appBindData, applicationInfo); + + // activityThread.mBoundApplication = appBindData; + Field mBoundApplicationField = ACTIVITY_THREAD_CLASS.getDeclaredField("mBoundApplication"); + mBoundApplicationField.setAccessible(true); + mBoundApplicationField.set(ACTIVITY_THREAD, appBindData); + } catch (Throwable throwable) { + // this is a workaround, so failing is not an error + Ln.d("Could not fill app info: " + throwable.getMessage()); + } + } + + private static void fillAppContext() { + try { + Application app = Instrumentation.newApplication(Application.class, FakeContext.get()); + + // activityThread.mInitialApplication = app; + Field mInitialApplicationField = ACTIVITY_THREAD_CLASS.getDeclaredField("mInitialApplication"); + mInitialApplicationField.setAccessible(true); + mInitialApplicationField.set(ACTIVITY_THREAD, app); + } catch (Throwable throwable) { + // this is a workaround, so failing is not an error + Ln.d("Could not fill app context: " + throwable.getMessage()); + } + } + + private static void fillConfigurationController() { + try { + Class configurationControllerClass = Class.forName("android.app.ConfigurationController"); + Class activityThreadInternalClass = Class.forName("android.app.ActivityThreadInternal"); + + // configurationController = new ConfigurationController(ACTIVITY_THREAD); + Constructor configurationControllerConstructor = configurationControllerClass.getDeclaredConstructor(activityThreadInternalClass); + configurationControllerConstructor.setAccessible(true); + Object configurationController = configurationControllerConstructor.newInstance(ACTIVITY_THREAD); + + // ACTIVITY_THREAD.mConfigurationController = configurationController; + Field configurationControllerField = ACTIVITY_THREAD_CLASS.getDeclaredField("mConfigurationController"); + configurationControllerField.setAccessible(true); + configurationControllerField.set(ACTIVITY_THREAD, configurationController); + } catch (Throwable throwable) { + Ln.d("Could not fill configuration: " + throwable.getMessage()); + } + } + + static Context getSystemContext() { + try { + Method getSystemContextMethod = ACTIVITY_THREAD_CLASS.getDeclaredMethod("getSystemContext"); + return (Context) getSystemContextMethod.invoke(ACTIVITY_THREAD); + } catch (Throwable throwable) { + // this is a workaround, so failing is not an error + Ln.d("Could not get system context: " + throwable.getMessage()); + return null; + } + } + + @TargetApi(AndroidVersions.API_30_ANDROID_11) + @SuppressLint("WrongConstant,MissingPermission") + public static AudioRecord createAudioRecord(int source, int sampleRate, int channelConfig, int channels, int channelMask, int encoding) throws + AudioCaptureException { + // Vivo (and maybe some other third-party ROMs) modified `AudioRecord`'s constructor, requiring `Context`s from real App environment. + // + // This method invokes the `AudioRecord(long nativeRecordInJavaObj)` constructor to create an empty `AudioRecord` instance, then uses + // reflections to initialize it like the normal constructor do (or the `AudioRecord.Builder.build()` method do). + // As a result, the modified code was not executed. + try { + // AudioRecord audioRecord = new AudioRecord(0L); + Constructor audioRecordConstructor = AudioRecord.class.getDeclaredConstructor(long.class); + audioRecordConstructor.setAccessible(true); + AudioRecord audioRecord = audioRecordConstructor.newInstance(0L); + + // audioRecord.mRecordingState = RECORDSTATE_STOPPED; + Field mRecordingStateField = AudioRecord.class.getDeclaredField("mRecordingState"); + mRecordingStateField.setAccessible(true); + mRecordingStateField.set(audioRecord, AudioRecord.RECORDSTATE_STOPPED); + + Looper looper = Looper.myLooper(); + if (looper == null) { + looper = Looper.getMainLooper(); + } + + // audioRecord.mInitializationLooper = looper; + Field mInitializationLooperField = AudioRecord.class.getDeclaredField("mInitializationLooper"); + mInitializationLooperField.setAccessible(true); + mInitializationLooperField.set(audioRecord, looper); + + // Create `AudioAttributes` with fixed capture preset + int capturePreset = source; + AudioAttributes.Builder audioAttributesBuilder = new AudioAttributes.Builder(); + Method setInternalCapturePresetMethod = AudioAttributes.Builder.class.getMethod("setInternalCapturePreset", int.class); + setInternalCapturePresetMethod.invoke(audioAttributesBuilder, capturePreset); + AudioAttributes attributes = audioAttributesBuilder.build(); + + // audioRecord.mAudioAttributes = attributes; + Field mAudioAttributesField = AudioRecord.class.getDeclaredField("mAudioAttributes"); + mAudioAttributesField.setAccessible(true); + mAudioAttributesField.set(audioRecord, attributes); + + // audioRecord.audioParamCheck(capturePreset, sampleRate, encoding); + Method audioParamCheckMethod = AudioRecord.class.getDeclaredMethod("audioParamCheck", int.class, int.class, int.class); + audioParamCheckMethod.setAccessible(true); + audioParamCheckMethod.invoke(audioRecord, capturePreset, sampleRate, encoding); + + // audioRecord.mChannelCount = channels + Field mChannelCountField = AudioRecord.class.getDeclaredField("mChannelCount"); + mChannelCountField.setAccessible(true); + mChannelCountField.set(audioRecord, channels); + + // audioRecord.mChannelMask = channelMask + Field mChannelMaskField = AudioRecord.class.getDeclaredField("mChannelMask"); + mChannelMaskField.setAccessible(true); + mChannelMaskField.set(audioRecord, channelMask); + + int minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, encoding); + int bufferSizeInBytes = minBufferSize * 8; + + // audioRecord.audioBuffSizeCheck(bufferSizeInBytes) + Method audioBuffSizeCheckMethod = AudioRecord.class.getDeclaredMethod("audioBuffSizeCheck", int.class); + audioBuffSizeCheckMethod.setAccessible(true); + audioBuffSizeCheckMethod.invoke(audioRecord, bufferSizeInBytes); + + final int channelIndexMask = 0; + + int[] sampleRateArray = new int[]{sampleRate}; + int[] session = new int[]{AudioManager.AUDIO_SESSION_ID_GENERATE}; + + int initResult; + if (Build.VERSION.SDK_INT < AndroidVersions.API_31_ANDROID_12) { + // private native final int native_setup(Object audiorecord_this, + // Object /*AudioAttributes*/ attributes, + // int[] sampleRate, int channelMask, int channelIndexMask, int audioFormat, + // int buffSizeInBytes, int[] sessionId, String opPackageName, + // long nativeRecordInJavaObj); + Method nativeSetupMethod = AudioRecord.class.getDeclaredMethod("native_setup", Object.class, Object.class, int[].class, int.class, + int.class, int.class, int.class, int[].class, String.class, long.class); + nativeSetupMethod.setAccessible(true); + initResult = (int) nativeSetupMethod.invoke(audioRecord, new WeakReference(audioRecord), attributes, sampleRateArray, + channelMask, channelIndexMask, audioRecord.getAudioFormat(), bufferSizeInBytes, session, FakeContext.get().getOpPackageName(), + 0L); + } else { + // Assume `context` is never `null` + AttributionSource attributionSource = FakeContext.get().getAttributionSource(); + + // Assume `attributionSource.getPackageName()` is never null + + // ScopedParcelState attributionSourceState = attributionSource.asScopedParcelState() + Method asScopedParcelStateMethod = AttributionSource.class.getDeclaredMethod("asScopedParcelState"); + asScopedParcelStateMethod.setAccessible(true); + + try (AutoCloseable attributionSourceState = (AutoCloseable) asScopedParcelStateMethod.invoke(attributionSource)) { + Method getParcelMethod = attributionSourceState.getClass().getDeclaredMethod("getParcel"); + Parcel attributionSourceParcel = (Parcel) getParcelMethod.invoke(attributionSourceState); + + if (Build.VERSION.SDK_INT < AndroidVersions.API_34_ANDROID_14) { + // private native int native_setup(Object audiorecordThis, + // Object /*AudioAttributes*/ attributes, + // int[] sampleRate, int channelMask, int channelIndexMask, int audioFormat, + // int buffSizeInBytes, int[] sessionId, @NonNull Parcel attributionSource, + // long nativeRecordInJavaObj, int maxSharedAudioHistoryMs); + Method nativeSetupMethod = AudioRecord.class.getDeclaredMethod("native_setup", Object.class, Object.class, int[].class, + int.class, int.class, int.class, int.class, int[].class, Parcel.class, long.class, int.class); + nativeSetupMethod.setAccessible(true); + initResult = (int) nativeSetupMethod.invoke(audioRecord, new WeakReference(audioRecord), attributes, + sampleRateArray, channelMask, channelIndexMask, audioRecord.getAudioFormat(), bufferSizeInBytes, session, + attributionSourceParcel, 0L, 0); + } else { + // Android 14 added a new int parameter "halInputFlags" + // + Method nativeSetupMethod = AudioRecord.class.getDeclaredMethod("native_setup", Object.class, Object.class, int[].class, + int.class, int.class, int.class, int.class, int[].class, Parcel.class, long.class, int.class, int.class); + nativeSetupMethod.setAccessible(true); + initResult = (int) nativeSetupMethod.invoke(audioRecord, new WeakReference(audioRecord), attributes, + sampleRateArray, channelMask, channelIndexMask, audioRecord.getAudioFormat(), bufferSizeInBytes, session, + attributionSourceParcel, 0L, 0, 0); + } + } + } + + if (initResult != AudioRecord.SUCCESS) { + Ln.e("Error code " + initResult + " when initializing native AudioRecord object."); + throw new RuntimeException("Cannot create AudioRecord"); + } + + // mSampleRate = sampleRate[0] + Field mSampleRateField = AudioRecord.class.getDeclaredField("mSampleRate"); + mSampleRateField.setAccessible(true); + mSampleRateField.set(audioRecord, sampleRateArray[0]); + + // audioRecord.mSessionId = session[0] + Field mSessionIdField = AudioRecord.class.getDeclaredField("mSessionId"); + mSessionIdField.setAccessible(true); + mSessionIdField.set(audioRecord, session[0]); + + // audioRecord.mState = AudioRecord.STATE_INITIALIZED + Field mStateField = AudioRecord.class.getDeclaredField("mState"); + mStateField.setAccessible(true); + mStateField.set(audioRecord, AudioRecord.STATE_INITIALIZED); + + return audioRecord; + } catch (Exception e) { + Ln.e("Cannot create AudioRecord", e); + throw new AudioCaptureException(); + } + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/audio/AudioCapture.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/audio/AudioCapture.java new file mode 100644 index 0000000..62903f8 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/audio/AudioCapture.java @@ -0,0 +1,20 @@ +package com.genymobile.scrcpy.audio; + +import android.media.MediaCodec; + +import java.nio.ByteBuffer; + +public interface AudioCapture { + void checkCompatibility() throws AudioCaptureException; + void start() throws AudioCaptureException; + void stop(); + + /** + * Read a chunk of {@link AudioConfig#MAX_READ_SIZE} samples. + * + * @param outDirectBuffer The target buffer + * @param outBufferInfo The info to provide to MediaCodec + * @return the number of bytes actually read. + */ + int read(ByteBuffer outDirectBuffer, MediaCodec.BufferInfo outBufferInfo); +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/audio/AudioCaptureException.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/audio/AudioCaptureException.java new file mode 100644 index 0000000..4b0b7e8 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/audio/AudioCaptureException.java @@ -0,0 +1,12 @@ +package com.genymobile.scrcpy.audio; + +/** + * Exception for any audio capture issue. + *

+ * This includes the case where audio capture failed on Android 11 specifically because the running App (Shell) was not in foreground. + *

+ * Its purpose is to disable audio without errors (that's why the exception is empty, any error message must be printed by the caller before + * throwing the exception). + */ +public class AudioCaptureException extends Exception { +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/audio/AudioCodec.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/audio/AudioCodec.java new file mode 100644 index 0000000..5e20a2a --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/audio/AudioCodec.java @@ -0,0 +1,51 @@ +package com.genymobile.scrcpy.audio; + +import com.genymobile.scrcpy.model.Codec; + +import android.media.MediaFormat; + +public enum AudioCodec implements Codec { + OPUS(0x6f_70_75_73, "opus", MediaFormat.MIMETYPE_AUDIO_OPUS), + AAC(0x00_61_61_63, "aac", MediaFormat.MIMETYPE_AUDIO_AAC), + FLAC(0x66_6c_61_63, "flac", MediaFormat.MIMETYPE_AUDIO_FLAC), + RAW(0x00_72_61_77, "raw", MediaFormat.MIMETYPE_AUDIO_RAW); + + private final int id; // 4-byte ASCII representation of the name + private final String name; + private final String mimeType; + + AudioCodec(int id, String name, String mimeType) { + this.id = id; + this.name = name; + this.mimeType = mimeType; + } + + @Override + public Type getType() { + return Type.AUDIO; + } + + @Override + public int getId() { + return id; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getMimeType() { + return mimeType; + } + + public static AudioCodec findByName(String name) { + for (AudioCodec codec : values()) { + if (codec.name.equals(name)) { + return codec; + } + } + return null; + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/audio/AudioConfig.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/audio/AudioConfig.java new file mode 100644 index 0000000..c77165a --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/audio/AudioConfig.java @@ -0,0 +1,29 @@ +package com.genymobile.scrcpy.audio; + +import android.media.AudioFormat; + +public final class AudioConfig { + public static final int SAMPLE_RATE = 48000; + public static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_STEREO; + public static final int CHANNELS = 2; + public static final int CHANNEL_MASK = AudioFormat.CHANNEL_IN_LEFT | AudioFormat.CHANNEL_IN_RIGHT; + public static final int ENCODING = AudioFormat.ENCODING_PCM_16BIT; + public static final int BYTES_PER_SAMPLE = 2; + + // Never read more than 1024 samples, even if the buffer is bigger (that would increase latency). + // A lower value is useless, since the system captures audio samples by blocks of 1024 (so for example if we read by blocks of 256 samples, we + // receive 4 successive blocks without waiting, then we wait for the 4 next ones). + public static final int MAX_READ_SIZE = 1024 * CHANNELS * BYTES_PER_SAMPLE; + + private AudioConfig() { + // Not instantiable + } + + public static AudioFormat createAudioFormat() { + AudioFormat.Builder builder = new AudioFormat.Builder(); + builder.setEncoding(ENCODING); + builder.setSampleRate(SAMPLE_RATE); + builder.setChannelMask(CHANNEL_CONFIG); + return builder.build(); + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/audio/AudioDirectCapture.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/audio/AudioDirectCapture.java new file mode 100644 index 0000000..bf870be --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/audio/AudioDirectCapture.java @@ -0,0 +1,142 @@ +package com.genymobile.scrcpy.audio; + +import com.genymobile.scrcpy.AndroidVersions; +import com.genymobile.scrcpy.FakeContext; +import com.genymobile.scrcpy.Workarounds; +import com.genymobile.scrcpy.util.Ln; +import com.genymobile.scrcpy.wrappers.ServiceManager; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.content.ComponentName; +import android.content.Intent; +import android.media.AudioRecord; +import android.media.MediaCodec; +import android.os.Build; +import android.os.SystemClock; + +import java.nio.ByteBuffer; + +public class AudioDirectCapture implements AudioCapture { + + private static final int SAMPLE_RATE = AudioConfig.SAMPLE_RATE; + private static final int CHANNEL_CONFIG = AudioConfig.CHANNEL_CONFIG; + private static final int CHANNELS = AudioConfig.CHANNELS; + private static final int CHANNEL_MASK = AudioConfig.CHANNEL_MASK; + private static final int ENCODING = AudioConfig.ENCODING; + + private final int audioSource; + + private AudioRecord recorder; + private AudioRecordReader reader; + + public AudioDirectCapture(AudioSource audioSource) { + this.audioSource = audioSource.getDirectAudioSource(); + } + + @TargetApi(AndroidVersions.API_23_ANDROID_6_0) + @SuppressLint({"WrongConstant", "MissingPermission"}) + private static AudioRecord createAudioRecord(int audioSource) { + AudioRecord.Builder builder = new AudioRecord.Builder(); + if (Build.VERSION.SDK_INT >= AndroidVersions.API_31_ANDROID_12) { + // On older APIs, Workarounds.fillAppInfo() must be called beforehand + builder.setContext(FakeContext.get()); + } + builder.setAudioSource(audioSource); + builder.setAudioFormat(AudioConfig.createAudioFormat()); + int minBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, ENCODING); + if (minBufferSize > 0) { + // This buffer size does not impact latency + builder.setBufferSizeInBytes(8 * minBufferSize); + } + + return builder.build(); + } + + private static void startWorkaroundAndroid11() { + // Android 11 requires Apps to be at foreground to record audio. + // Normally, each App has its own user ID, so Android checks whether the requesting App has the user ID that's at the foreground. + // But scrcpy server is NOT an App, it's a Java application started from Android shell, so it has the same user ID (2000) with Android + // shell ("com.android.shell"). + // If there is an Activity from Android shell running at foreground, then the permission system will believe scrcpy is also in the + // foreground. + Intent intent = new Intent(Intent.ACTION_MAIN); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.addCategory(Intent.CATEGORY_LAUNCHER); + intent.setComponent(new ComponentName(FakeContext.PACKAGE_NAME, "com.android.shell.HeapDumpActivity")); + ServiceManager.getActivityManager().startActivity(intent); + } + + private static void stopWorkaroundAndroid11() { + ServiceManager.getActivityManager().forceStopPackage(FakeContext.PACKAGE_NAME); + } + + private void tryStartRecording(int attempts, int delayMs) throws AudioCaptureException { + while (attempts-- > 0) { + // Wait for activity to start + SystemClock.sleep(delayMs); + try { + startRecording(); + return; // it worked + } catch (UnsupportedOperationException e) { + if (attempts == 0) { + Ln.e("Failed to start audio capture"); + Ln.e("On Android 11, audio capture must be started in the foreground, make sure that the device is unlocked when starting " + + "scrcpy."); + throw new AudioCaptureException(); + } else { + Ln.d("Failed to start audio capture, retrying..."); + } + } + } + } + + private void startRecording() throws AudioCaptureException { + try { + recorder = createAudioRecord(audioSource); + } catch (NullPointerException e) { + // Creating an AudioRecord using an AudioRecord.Builder does not work on Vivo phones: + // - + // - + recorder = Workarounds.createAudioRecord(audioSource, SAMPLE_RATE, CHANNEL_CONFIG, CHANNELS, CHANNEL_MASK, ENCODING); + } + recorder.startRecording(); + reader = new AudioRecordReader(recorder); + } + + @Override + public void checkCompatibility() throws AudioCaptureException { + if (Build.VERSION.SDK_INT < AndroidVersions.API_30_ANDROID_11) { + Ln.w("Audio disabled: it is not supported before Android 11"); + throw new AudioCaptureException(); + } + } + + @Override + public void start() throws AudioCaptureException { + if (Build.VERSION.SDK_INT == AndroidVersions.API_30_ANDROID_11) { + startWorkaroundAndroid11(); + try { + tryStartRecording(5, 100); + } finally { + stopWorkaroundAndroid11(); + } + } else { + startRecording(); + } + } + + @Override + public void stop() { + if (recorder != null) { + // Will call .stop() if necessary, without throwing an IllegalStateException + recorder.release(); + } + } + + @Override + @TargetApi(AndroidVersions.API_24_ANDROID_7_0) + public int read(ByteBuffer outDirectBuffer, MediaCodec.BufferInfo outBufferInfo) { + return reader.read(outDirectBuffer, outBufferInfo); + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/audio/AudioEncoder.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/audio/AudioEncoder.java new file mode 100644 index 0000000..1be21eb --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/audio/AudioEncoder.java @@ -0,0 +1,380 @@ +package com.genymobile.scrcpy.audio; + +import com.genymobile.scrcpy.AndroidVersions; +import com.genymobile.scrcpy.AsyncProcessor; +import com.genymobile.scrcpy.Options; +import com.genymobile.scrcpy.device.Streamer; +import com.genymobile.scrcpy.model.Codec; +import com.genymobile.scrcpy.model.CodecOption; +import com.genymobile.scrcpy.model.ConfigurationException; +import com.genymobile.scrcpy.util.CodecUtils; +import com.genymobile.scrcpy.util.IO; +import com.genymobile.scrcpy.util.Ln; +import com.genymobile.scrcpy.util.LogUtils; + +import android.annotation.TargetApi; +import android.media.MediaCodec; +import android.media.MediaFormat; +import android.os.Build; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; + +public final class AudioEncoder implements AsyncProcessor { + + private static class InputTask { + private final int index; + + InputTask(int index) { + this.index = index; + } + } + + private static class OutputTask { + private final int index; + private final MediaCodec.BufferInfo bufferInfo; + + OutputTask(int index, MediaCodec.BufferInfo bufferInfo) { + this.index = index; + this.bufferInfo = bufferInfo; + } + } + + private static final int SAMPLE_RATE = AudioConfig.SAMPLE_RATE; + private static final int CHANNELS = AudioConfig.CHANNELS; + + private final AudioCapture capture; + private final Streamer streamer; + private final int bitRate; + private final List codecOptions; + private final String encoderName; + + private boolean recreatePts; + private long previousPts; + + // Capacity of 64 is in practice "infinite" (it is limited by the number of available MediaCodec buffers, typically 4). + // So many pending tasks would lead to an unacceptable delay anyway. + private final BlockingQueue inputTasks = new ArrayBlockingQueue<>(64); + private final BlockingQueue outputTasks = new ArrayBlockingQueue<>(64); + + private Thread thread; + private HandlerThread mediaCodecThread; + + private Thread inputThread; + private Thread outputThread; + + private boolean ended; + + public AudioEncoder(AudioCapture capture, Streamer streamer, Options options) { + this.capture = capture; + this.streamer = streamer; + this.bitRate = options.getAudioBitRate(); + this.codecOptions = options.getAudioCodecOptions(); + this.encoderName = options.getAudioEncoder(); + } + + private static MediaFormat createFormat(String mimeType, int bitRate, List codecOptions) { + MediaFormat format = new MediaFormat(); + format.setString(MediaFormat.KEY_MIME, mimeType); + format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate); + format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, CHANNELS); + format.setInteger(MediaFormat.KEY_SAMPLE_RATE, SAMPLE_RATE); + + if (codecOptions != null) { + for (CodecOption option : codecOptions) { + String key = option.getKey(); + Object value = option.getValue(); + CodecUtils.setCodecOption(format, key, value); + Ln.d("Audio codec option set: " + key + " (" + value.getClass().getSimpleName() + ") = " + value); + } + } + + return format; + } + + @TargetApi(AndroidVersions.API_24_ANDROID_7_0) + private void inputThread(MediaCodec mediaCodec, AudioCapture capture) throws IOException, InterruptedException { + final MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); + + while (!Thread.currentThread().isInterrupted()) { + InputTask task = inputTasks.take(); + ByteBuffer buffer = mediaCodec.getInputBuffer(task.index); + int r = capture.read(buffer, bufferInfo); + if (r <= 0) { + throw new IOException("Could not read audio: " + r); + } + + mediaCodec.queueInputBuffer(task.index, bufferInfo.offset, bufferInfo.size, bufferInfo.presentationTimeUs, bufferInfo.flags); + } + } + + private void outputThread(MediaCodec mediaCodec) throws IOException, InterruptedException { + streamer.writeAudioHeader(); + + while (!Thread.currentThread().isInterrupted()) { + OutputTask task = outputTasks.take(); + ByteBuffer buffer = mediaCodec.getOutputBuffer(task.index); + try { + if (recreatePts) { + fixTimestamp(task.bufferInfo); + } + streamer.writePacket(buffer, task.bufferInfo); + } finally { + mediaCodec.releaseOutputBuffer(task.index, false); + } + } + } + + private void fixTimestamp(MediaCodec.BufferInfo bufferInfo) { + assert recreatePts; + + if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { + // Config packet, nothing to fix + return; + } + + long pts = bufferInfo.presentationTimeUs; + if (previousPts != 0) { + long now = System.nanoTime() / 1000; + // This specific encoder produces PTS matching the exact number of samples + long duration = pts - previousPts; + bufferInfo.presentationTimeUs = now - duration; + } + + previousPts = pts; + } + + @Override + public void start(TerminationListener listener) { + thread = new Thread(() -> { + boolean fatalError = false; + try { + encode(); + } catch (ConfigurationException e) { + // Do not print stack trace, a user-friendly error-message has already been logged + fatalError = true; + } catch (AudioCaptureException e) { + // Do not print stack trace, a user-friendly error-message has already been logged + } catch (IOException e) { + Ln.e("Audio encoding error", e); + fatalError = true; + } finally { + Ln.d("Audio encoder stopped"); + listener.onTerminated(fatalError); + } + }, "audio-encoder"); + thread.start(); + } + + @Override + public void stop() { + if (thread != null) { + // Just wake up the blocking wait from the thread, so that it properly releases all its resources and terminates + end(); + } + } + + @Override + public void join() throws InterruptedException { + if (thread != null) { + thread.join(); + } + } + + private synchronized void end() { + ended = true; + notify(); + } + + private synchronized void waitEnded() { + try { + while (!ended) { + wait(); + } + } catch (InterruptedException e) { + // ignore + } + } + + @TargetApi(AndroidVersions.API_23_ANDROID_6_0) + private void encode() throws IOException, ConfigurationException, AudioCaptureException { + if (Build.VERSION.SDK_INT < AndroidVersions.API_30_ANDROID_11) { + Ln.w("Audio disabled: it is not supported before Android 11"); + streamer.writeDisableStream(false); + return; + } + + MediaCodec mediaCodec = null; + + boolean mediaCodecStarted = false; + try { + capture.checkCompatibility(); // throws an AudioCaptureException on error + + Codec codec = streamer.getCodec(); + mediaCodec = createMediaCodec(codec, encoderName); + + // The default OPUS and FLAC encoders overwrite the input PTS with a value that matches the number of samples. This is not the behavior + // we want: it ignores any audio clock drift and hard silences (packets not produced on silence). To work around this behavior, + // regenerate PTS based on the current time and the packet duration. + String codecName = mediaCodec.getCanonicalName(); + recreatePts = "c2.android.opus.encoder".equals(codecName) || "c2.android.flac.encoder".equals(codecName); + + mediaCodecThread = new HandlerThread("media-codec"); + mediaCodecThread.start(); + + MediaFormat format = createFormat(codec.getMimeType(), bitRate, codecOptions); + mediaCodec.setCallback(new EncoderCallback(), new Handler(mediaCodecThread.getLooper())); + mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); + + capture.start(); + + final MediaCodec mediaCodecRef = mediaCodec; + inputThread = new Thread(() -> { + try { + inputThread(mediaCodecRef, capture); + } catch (IOException | InterruptedException e) { + Ln.e("Audio capture error", e); + } finally { + end(); + } + }, "audio-in"); + + outputThread = new Thread(() -> { + try { + outputThread(mediaCodecRef); + } catch (InterruptedException e) { + // this is expected on close + } catch (IOException e) { + // Broken pipe is expected on close, because the socket is closed by the client + if (!IO.isBrokenPipe(e)) { + Ln.e("Audio encoding error", e); + } + } finally { + end(); + } + }, "audio-out"); + + mediaCodec.start(); + mediaCodecStarted = true; + inputThread.start(); + outputThread.start(); + + waitEnded(); + } catch (AudioCaptureException e) { + // Notify the client that the audio could not be captured + streamer.writeDisableStream(false); + throw e; + } catch (Throwable e) { + // Notify the error to make scrcpy exit + streamer.writeDisableStream(true); + throw e; + } finally { + // Cleanup everything (either at the end or on error at any step of the initialization) + if (mediaCodecThread != null) { + Looper looper = mediaCodecThread.getLooper(); + if (looper != null) { + looper.quitSafely(); + } + } + if (inputThread != null) { + inputThread.interrupt(); + } + if (outputThread != null) { + outputThread.interrupt(); + } + + try { + if (mediaCodecThread != null) { + mediaCodecThread.join(); + } + if (inputThread != null) { + inputThread.join(); + } + if (outputThread != null) { + outputThread.join(); + } + } catch (InterruptedException e) { + // Should never happen + throw new AssertionError(e); + } + + if (mediaCodec != null) { + if (mediaCodecStarted) { + mediaCodec.stop(); + } + mediaCodec.release(); + } + if (capture != null) { + capture.stop(); + } + } + } + + private static MediaCodec createMediaCodec(Codec codec, String encoderName) throws IOException, ConfigurationException { + if (encoderName != null) { + Ln.d("Creating audio encoder by name: '" + encoderName + "'"); + try { + MediaCodec mediaCodec = MediaCodec.createByCodecName(encoderName); + String mimeType = Codec.getMimeType(mediaCodec); + if (!codec.getMimeType().equals(mimeType)) { + Ln.e("Audio encoder type for \"" + encoderName + "\" (" + mimeType + ") does not match codec type (" + codec.getMimeType() + ")"); + throw new ConfigurationException("Incorrect encoder type: " + encoderName); + } + return mediaCodec; + } catch (IllegalArgumentException e) { + Ln.e("Audio encoder '" + encoderName + "' for " + codec.getName() + " not found\n" + LogUtils.buildAudioEncoderListMessage()); + throw new ConfigurationException("Unknown encoder: " + encoderName); + } catch (IOException e) { + Ln.e("Could not create audio encoder '" + encoderName + "' for " + codec.getName() + "\n" + LogUtils.buildAudioEncoderListMessage()); + throw e; + } + } + + try { + MediaCodec mediaCodec = MediaCodec.createEncoderByType(codec.getMimeType()); + Ln.d("Using audio encoder: '" + mediaCodec.getName() + "'"); + return mediaCodec; + } catch (IOException | IllegalArgumentException e) { + Ln.e("Could not create default audio encoder for " + codec.getName() + "\n" + LogUtils.buildAudioEncoderListMessage()); + throw e; + } + } + + private final class EncoderCallback extends MediaCodec.Callback { + @TargetApi(AndroidVersions.API_24_ANDROID_7_0) + @Override + public void onInputBufferAvailable(MediaCodec codec, int index) { + try { + inputTasks.put(new InputTask(index)); + } catch (InterruptedException e) { + end(); + } + } + + @Override + public void onOutputBufferAvailable(MediaCodec codec, int index, MediaCodec.BufferInfo bufferInfo) { + try { + outputTasks.put(new OutputTask(index, bufferInfo)); + } catch (InterruptedException e) { + end(); + } + } + + @Override + public void onError(MediaCodec codec, MediaCodec.CodecException e) { + Ln.e("MediaCodec error", e); + end(); + } + + @Override + public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) { + // ignore + } + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/audio/AudioPlaybackCapture.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/audio/AudioPlaybackCapture.java new file mode 100644 index 0000000..009a239 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/audio/AudioPlaybackCapture.java @@ -0,0 +1,138 @@ +package com.genymobile.scrcpy.audio; + +import com.genymobile.scrcpy.AndroidVersions; +import com.genymobile.scrcpy.FakeContext; +import com.genymobile.scrcpy.util.Ln; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.content.Context; +import android.media.AudioAttributes; +import android.media.AudioFormat; +import android.media.AudioManager; +import android.media.AudioRecord; +import android.media.MediaCodec; +import android.os.Build; + +import java.lang.reflect.Method; +import java.nio.ByteBuffer; + +public final class AudioPlaybackCapture implements AudioCapture { + + private final boolean keepPlayingOnDevice; + + private AudioRecord recorder; + private AudioRecordReader reader; + + public AudioPlaybackCapture(boolean keepPlayingOnDevice) { + this.keepPlayingOnDevice = keepPlayingOnDevice; + } + + @SuppressLint("PrivateApi") + private AudioRecord createAudioRecord() throws AudioCaptureException { + // See + try { + Class audioMixingRuleClass = Class.forName("android.media.audiopolicy.AudioMixingRule"); + Class audioMixingRuleBuilderClass = Class.forName("android.media.audiopolicy.AudioMixingRule$Builder"); + + // AudioMixingRule.Builder audioMixingRuleBuilder = new AudioMixingRule.Builder(); + Object audioMixingRuleBuilder = audioMixingRuleBuilderClass.getConstructor().newInstance(); + + // audioMixingRuleBuilder.setTargetMixRole(AudioMixingRule.MIX_ROLE_PLAYERS); + int mixRolePlayersConstant = audioMixingRuleClass.getField("MIX_ROLE_PLAYERS").getInt(null); + Method setTargetMixRoleMethod = audioMixingRuleBuilderClass.getMethod("setTargetMixRole", int.class); + setTargetMixRoleMethod.invoke(audioMixingRuleBuilder, mixRolePlayersConstant); + + AudioAttributes attributes = new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build(); + + // audioMixingRuleBuilder.addMixRule(AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE, attributes); + int ruleMatchAttributeUsageConstant = audioMixingRuleClass.getField("RULE_MATCH_ATTRIBUTE_USAGE").getInt(null); + Method addMixRuleMethod = audioMixingRuleBuilderClass.getMethod("addMixRule", int.class, Object.class); + addMixRuleMethod.invoke(audioMixingRuleBuilder, ruleMatchAttributeUsageConstant, attributes); + + // AudioMixingRule audioMixingRule = builder.build(); + Object audioMixingRule = audioMixingRuleBuilderClass.getMethod("build").invoke(audioMixingRuleBuilder); + + // audioMixingRuleBuilder.voiceCommunicationCaptureAllowed(true); + Method voiceCommunicationCaptureAllowedMethod = audioMixingRuleBuilderClass.getMethod("voiceCommunicationCaptureAllowed", boolean.class); + voiceCommunicationCaptureAllowedMethod.invoke(audioMixingRuleBuilder, true); + + Class audioMixClass = Class.forName("android.media.audiopolicy.AudioMix"); + Class audioMixBuilderClass = Class.forName("android.media.audiopolicy.AudioMix$Builder"); + + // AudioMix.Builder audioMixBuilder = new AudioMix.Builder(audioMixingRule); + Object audioMixBuilder = audioMixBuilderClass.getConstructor(audioMixingRuleClass).newInstance(audioMixingRule); + + // audioMixBuilder.setFormat(createAudioFormat()); + Method setFormat = audioMixBuilder.getClass().getMethod("setFormat", AudioFormat.class); + setFormat.invoke(audioMixBuilder, AudioConfig.createAudioFormat()); + + String routeFlagName = keepPlayingOnDevice ? "ROUTE_FLAG_LOOP_BACK_RENDER" : "ROUTE_FLAG_LOOP_BACK"; + int routeFlags = audioMixClass.getField(routeFlagName).getInt(null); + + // audioMixBuilder.setRouteFlags(routeFlag); + Method setRouteFlags = audioMixBuilder.getClass().getMethod("setRouteFlags", int.class); + setRouteFlags.invoke(audioMixBuilder, routeFlags); + + // AudioMix audioMix = audioMixBuilder.build(); + Object audioMix = audioMixBuilderClass.getMethod("build").invoke(audioMixBuilder); + + Class audioPolicyClass = Class.forName("android.media.audiopolicy.AudioPolicy"); + Class audioPolicyBuilderClass = Class.forName("android.media.audiopolicy.AudioPolicy$Builder"); + + // AudioPolicy.Builder audioPolicyBuilder = new AudioPolicy.Builder(); + Object audioPolicyBuilder = audioPolicyBuilderClass.getConstructor(Context.class).newInstance(FakeContext.get()); + + // audioPolicyBuilder.addMix(audioMix); + Method addMixMethod = audioPolicyBuilderClass.getMethod("addMix", audioMixClass); + addMixMethod.invoke(audioPolicyBuilder, audioMix); + + // AudioPolicy audioPolicy = audioPolicyBuilder.build(); + Object audioPolicy = audioPolicyBuilderClass.getMethod("build").invoke(audioPolicyBuilder); + + // AudioManager.registerAudioPolicyStatic(audioPolicy); + Method registerAudioPolicyStaticMethod = AudioManager.class.getDeclaredMethod("registerAudioPolicyStatic", audioPolicyClass); + registerAudioPolicyStaticMethod.setAccessible(true); + int result = (int) registerAudioPolicyStaticMethod.invoke(null, audioPolicy); + if (result != 0) { + throw new RuntimeException("registerAudioPolicy() returned " + result); + } + + // audioPolicy.createAudioRecordSink(audioPolicy); + Method createAudioRecordSinkClass = audioPolicyClass.getMethod("createAudioRecordSink", audioMixClass); + return (AudioRecord) createAudioRecordSinkClass.invoke(audioPolicy, audioMix); + } catch (Exception e) { + Ln.e("Could not capture audio playback", e); + throw new AudioCaptureException(); + } + } + + @Override + public void checkCompatibility() throws AudioCaptureException { + if (Build.VERSION.SDK_INT < AndroidVersions.API_33_ANDROID_13) { + Ln.w("Audio disabled: audio playback capture source not supported before Android 13"); + throw new AudioCaptureException(); + } + } + + @Override + public void start() throws AudioCaptureException { + recorder = createAudioRecord(); + recorder.startRecording(); + reader = new AudioRecordReader(recorder); + } + + @Override + public void stop() { + if (recorder != null) { + // Will call .stop() if necessary, without throwing an IllegalStateException + recorder.release(); + } + } + + @Override + @TargetApi(AndroidVersions.API_24_ANDROID_7_0) + public int read(ByteBuffer outDirectBuffer, MediaCodec.BufferInfo outBufferInfo) { + return reader.read(outDirectBuffer, outBufferInfo); + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/audio/AudioRawRecorder.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/audio/AudioRawRecorder.java new file mode 100644 index 0000000..9645bbb --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/audio/AudioRawRecorder.java @@ -0,0 +1,99 @@ +package com.genymobile.scrcpy.audio; + +import com.genymobile.scrcpy.AndroidVersions; +import com.genymobile.scrcpy.AsyncProcessor; +import com.genymobile.scrcpy.device.Streamer; +import com.genymobile.scrcpy.util.IO; +import com.genymobile.scrcpy.util.Ln; + +import android.media.MediaCodec; +import android.os.Build; + +import java.io.IOException; +import java.nio.ByteBuffer; + +public final class AudioRawRecorder implements AsyncProcessor { + + private final AudioCapture capture; + private final Streamer streamer; + + private Thread thread; + + public AudioRawRecorder(AudioCapture capture, Streamer streamer) { + this.capture = capture; + this.streamer = streamer; + } + + private void record() throws IOException, AudioCaptureException { + if (Build.VERSION.SDK_INT < AndroidVersions.API_30_ANDROID_11) { + Ln.w("Audio disabled: it is not supported before Android 11"); + streamer.writeDisableStream(false); + return; + } + + final ByteBuffer buffer = ByteBuffer.allocateDirect(AudioConfig.MAX_READ_SIZE); + final MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); + + try { + try { + capture.start(); + } catch (Throwable t) { + // Notify the client that the audio could not be captured + streamer.writeDisableStream(false); + throw t; + } + + streamer.writeAudioHeader(); + while (!Thread.currentThread().isInterrupted()) { + buffer.position(0); + int r = capture.read(buffer, bufferInfo); + if (r < 0) { + throw new IOException("Could not read audio: " + r); + } + buffer.limit(r); + + streamer.writePacket(buffer, bufferInfo); + } + } catch (IOException e) { + // Broken pipe is expected on close, because the socket is closed by the client + if (!IO.isBrokenPipe(e)) { + Ln.e("Audio capture error", e); + } + } finally { + capture.stop(); + } + } + + @Override + public void start(TerminationListener listener) { + thread = new Thread(() -> { + boolean fatalError = false; + try { + record(); + } catch (AudioCaptureException e) { + // Do not print stack trace, a user-friendly error-message has already been logged + } catch (Throwable t) { + Ln.e("Audio recording error", t); + fatalError = true; + } finally { + Ln.d("Audio recorder stopped"); + listener.onTerminated(fatalError); + } + }, "audio-raw"); + thread.start(); + } + + @Override + public void stop() { + if (thread != null) { + thread.interrupt(); + } + } + + @Override + public void join() throws InterruptedException { + if (thread != null) { + thread.join(); + } + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/audio/AudioRecordReader.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/audio/AudioRecordReader.java new file mode 100644 index 0000000..32b4225 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/audio/AudioRecordReader.java @@ -0,0 +1,67 @@ +package com.genymobile.scrcpy.audio; + +import com.genymobile.scrcpy.AndroidVersions; +import com.genymobile.scrcpy.util.Ln; + +import android.annotation.TargetApi; +import android.media.AudioRecord; +import android.media.AudioTimestamp; +import android.media.MediaCodec; + +import java.nio.ByteBuffer; + +public class AudioRecordReader { + + private static final long ONE_SAMPLE_US = + (1000000 + AudioConfig.SAMPLE_RATE - 1) / AudioConfig.SAMPLE_RATE; // 1 sample in microseconds (used for fixing PTS) + + private final AudioRecord recorder; + + private final AudioTimestamp timestamp = new AudioTimestamp(); + private long previousRecorderTimestamp = -1; + private long previousPts = 0; + private long nextPts = 0; + + public AudioRecordReader(AudioRecord recorder) { + this.recorder = recorder; + } + + @TargetApi(AndroidVersions.API_24_ANDROID_7_0) + public int read(ByteBuffer outDirectBuffer, MediaCodec.BufferInfo outBufferInfo) { + int r = recorder.read(outDirectBuffer, AudioConfig.MAX_READ_SIZE); + if (r <= 0) { + return r; + } + + long pts; + + int ret = recorder.getTimestamp(timestamp, AudioTimestamp.TIMEBASE_MONOTONIC); + if (ret == AudioRecord.SUCCESS && timestamp.nanoTime != previousRecorderTimestamp) { + pts = timestamp.nanoTime / 1000; + previousRecorderTimestamp = timestamp.nanoTime; + } else { + if (nextPts == 0) { + Ln.w("Could not get initial audio timestamp"); + nextPts = System.nanoTime() / 1000; + } + // compute from previous timestamp and packet size + pts = nextPts; + } + + long durationUs = r * 1000000L / (AudioConfig.CHANNELS * AudioConfig.BYTES_PER_SAMPLE * AudioConfig.SAMPLE_RATE); + nextPts = pts + durationUs; + + if (previousPts != 0 && pts < previousPts + ONE_SAMPLE_US) { + // Audio PTS may come from two sources: + // - recorder.getTimestamp() if the call works; + // - an estimation from the previous PTS and the packet size as a fallback. + // + // Therefore, the property that PTS are monotonically increasing is no guaranteed in corner cases, so enforce it. + pts = previousPts + ONE_SAMPLE_US; + } + previousPts = pts; + + outBufferInfo.set(0, r, pts, 0); + return r; + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/audio/AudioSource.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/audio/AudioSource.java new file mode 100644 index 0000000..d16b5e3 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/audio/AudioSource.java @@ -0,0 +1,45 @@ +package com.genymobile.scrcpy.audio; + +import android.annotation.SuppressLint; +import android.media.MediaRecorder; + +@SuppressLint("InlinedApi") +public enum AudioSource { + OUTPUT("output", MediaRecorder.AudioSource.REMOTE_SUBMIX), + MIC("mic", MediaRecorder.AudioSource.MIC), + PLAYBACK("playback", -1), + MIC_UNPROCESSED("mic-unprocessed", MediaRecorder.AudioSource.UNPROCESSED), + MIC_CAMCORDER("mic-camcorder", MediaRecorder.AudioSource.CAMCORDER), + MIC_VOICE_RECOGNITION("mic-voice-recognition", MediaRecorder.AudioSource.VOICE_RECOGNITION), + MIC_VOICE_COMMUNICATION("mic-voice-communication", MediaRecorder.AudioSource.VOICE_COMMUNICATION), + VOICE_CALL("voice-call", MediaRecorder.AudioSource.VOICE_CALL), + VOICE_CALL_UPLINK("voice-call-uplink", MediaRecorder.AudioSource.VOICE_UPLINK), + VOICE_CALL_DOWNLINK("voice-call-downlink", MediaRecorder.AudioSource.VOICE_DOWNLINK), + VOICE_PERFORMANCE("voice-performance", MediaRecorder.AudioSource.VOICE_PERFORMANCE); + + private final String name; + private final int directAudioSource; + + AudioSource(String name, int directAudioSource) { + this.name = name; + this.directAudioSource = directAudioSource; + } + + public boolean isDirect() { + return this != PLAYBACK; + } + + public int getDirectAudioSource() { + return directAudioSource; + } + + public static AudioSource findByName(String name) { + for (AudioSource audioSource : AudioSource.values()) { + if (name.equals(audioSource.name)) { + return audioSource; + } + } + + return null; + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/control/ControlChannel.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/control/ControlChannel.java new file mode 100644 index 0000000..2f12cdb --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/control/ControlChannel.java @@ -0,0 +1,24 @@ +package com.genymobile.scrcpy.control; + +import android.net.LocalSocket; + +import java.io.IOException; + +public final class ControlChannel { + + private final ControlMessageReader reader; + private final DeviceMessageWriter writer; + + public ControlChannel(LocalSocket controlSocket) throws IOException { + reader = new ControlMessageReader(controlSocket.getInputStream()); + writer = new DeviceMessageWriter(controlSocket.getOutputStream()); + } + + public ControlMessage recv() throws IOException { + return reader.read(); + } + + public void send(DeviceMessage msg) throws IOException { + writer.write(msg); + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/control/ControlMessage.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/control/ControlMessage.java new file mode 100644 index 0000000..777873b --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/control/ControlMessage.java @@ -0,0 +1,289 @@ +package com.genymobile.scrcpy.control; + +import com.genymobile.scrcpy.model.Position; + +/** + * Union of all supported event types, identified by their {@code type}. + */ +public final class ControlMessage { + + public static final int TYPE_INJECT_KEYCODE = 0; + public static final int TYPE_INJECT_TEXT = 1; + public static final int TYPE_INJECT_TOUCH_EVENT = 2; + public static final int TYPE_INJECT_SCROLL_EVENT = 3; + public static final int TYPE_BACK_OR_SCREEN_ON = 4; + public static final int TYPE_EXPAND_NOTIFICATION_PANEL = 5; + public static final int TYPE_EXPAND_SETTINGS_PANEL = 6; + public static final int TYPE_COLLAPSE_PANELS = 7; + public static final int TYPE_GET_CLIPBOARD = 8; + public static final int TYPE_SET_CLIPBOARD = 9; + public static final int TYPE_SET_DISPLAY_POWER = 10; + public static final int TYPE_ROTATE_DEVICE = 11; + public static final int TYPE_UHID_CREATE = 12; + public static final int TYPE_UHID_INPUT = 13; + public static final int TYPE_UHID_DESTROY = 14; + public static final int TYPE_OPEN_HARD_KEYBOARD_SETTINGS = 15; + public static final int TYPE_START_APP = 16; + public static final int TYPE_RESET_VIDEO = 17; + public static final int TYPE_CAMERA_SET_TORCH = 18; + public static final int TYPE_CAMERA_ZOOM_IN = 19; + public static final int TYPE_CAMERA_ZOOM_OUT = 20; + public static final int TYPE_RESIZE_DISPLAY = 21; + public static final int TYPE_SCAN_FILE = 22; + + public static final long SEQUENCE_INVALID = 0; + + public static final int COPY_KEY_NONE = 0; + public static final int COPY_KEY_COPY = 1; + public static final int COPY_KEY_CUT = 2; + + private int type; + private String text; + private int metaState; // KeyEvent.META_* + private int action; // KeyEvent.ACTION_* or MotionEvent.ACTION_* + private int keycode; // KeyEvent.KEYCODE_* + private int actionButton; // MotionEvent.BUTTON_* + private int buttons; // MotionEvent.BUTTON_* + private long pointerId; + private float pressure; + private Position position; + private float hScroll; + private float vScroll; + private int copyKey; + private boolean paste; + private int repeat; + private long sequence; + private int id; + private byte[] data; + private boolean on; + private int vendorId; + private int productId; + private int width; + private int height; + + private ControlMessage() { + } + + public static ControlMessage createInjectKeycode(int action, int keycode, int repeat, int metaState) { + ControlMessage msg = new ControlMessage(); + msg.type = TYPE_INJECT_KEYCODE; + msg.action = action; + msg.keycode = keycode; + msg.repeat = repeat; + msg.metaState = metaState; + return msg; + } + + public static ControlMessage createInjectText(String text) { + ControlMessage msg = new ControlMessage(); + msg.type = TYPE_INJECT_TEXT; + msg.text = text; + return msg; + } + + public static ControlMessage createInjectTouchEvent(int action, long pointerId, Position position, float pressure, int actionButton, + int buttons) { + ControlMessage msg = new ControlMessage(); + msg.type = TYPE_INJECT_TOUCH_EVENT; + msg.action = action; + msg.pointerId = pointerId; + msg.pressure = pressure; + msg.position = position; + msg.actionButton = actionButton; + msg.buttons = buttons; + return msg; + } + + public static ControlMessage createInjectScrollEvent(Position position, float hScroll, float vScroll, int buttons) { + ControlMessage msg = new ControlMessage(); + msg.type = TYPE_INJECT_SCROLL_EVENT; + msg.position = position; + msg.hScroll = hScroll; + msg.vScroll = vScroll; + msg.buttons = buttons; + return msg; + } + + public static ControlMessage createBackOrScreenOn(int action) { + ControlMessage msg = new ControlMessage(); + msg.type = TYPE_BACK_OR_SCREEN_ON; + msg.action = action; + return msg; + } + + public static ControlMessage createGetClipboard(int copyKey) { + ControlMessage msg = new ControlMessage(); + msg.type = TYPE_GET_CLIPBOARD; + msg.copyKey = copyKey; + return msg; + } + + public static ControlMessage createSetClipboard(long sequence, String text, boolean paste) { + ControlMessage msg = new ControlMessage(); + msg.type = TYPE_SET_CLIPBOARD; + msg.sequence = sequence; + msg.text = text; + msg.paste = paste; + return msg; + } + + public static ControlMessage createSetDisplayPower(boolean on) { + ControlMessage msg = new ControlMessage(); + msg.type = TYPE_SET_DISPLAY_POWER; + msg.on = on; + return msg; + } + + public static ControlMessage createEmpty(int type) { + ControlMessage msg = new ControlMessage(); + msg.type = type; + return msg; + } + + public static ControlMessage createUhidCreate(int id, int vendorId, int productId, String name, byte[] reportDesc) { + ControlMessage msg = new ControlMessage(); + msg.type = TYPE_UHID_CREATE; + msg.id = id; + msg.vendorId = vendorId; + msg.productId = productId; + msg.text = name; + msg.data = reportDesc; + return msg; + } + + public static ControlMessage createUhidInput(int id, byte[] data) { + ControlMessage msg = new ControlMessage(); + msg.type = TYPE_UHID_INPUT; + msg.id = id; + msg.data = data; + return msg; + } + + public static ControlMessage createUhidDestroy(int id) { + ControlMessage msg = new ControlMessage(); + msg.type = TYPE_UHID_DESTROY; + msg.id = id; + return msg; + } + + public static ControlMessage createStartApp(String name) { + ControlMessage msg = new ControlMessage(); + msg.type = TYPE_START_APP; + msg.text = name; + return msg; + } + + public static ControlMessage createCameraSetTorch(boolean on) { + ControlMessage msg = new ControlMessage(); + msg.type = TYPE_CAMERA_SET_TORCH; + msg.on = on; + return msg; + } + + public static ControlMessage createResizeDisplay(int width, int height) { + ControlMessage msg = new ControlMessage(); + msg.type = TYPE_RESIZE_DISPLAY; + msg.width = width; + msg.height = height; + return msg; + } + + public static ControlMessage createScanFile(String path) { + ControlMessage msg = new ControlMessage(); + msg.type = TYPE_SCAN_FILE; + msg.text = path; + return msg; + } + + public int getType() { + return type; + } + + public String getText() { + return text; + } + + public int getMetaState() { + return metaState; + } + + public int getAction() { + return action; + } + + public int getKeycode() { + return keycode; + } + + public int getActionButton() { + return actionButton; + } + + public int getButtons() { + return buttons; + } + + public long getPointerId() { + return pointerId; + } + + public float getPressure() { + return pressure; + } + + public Position getPosition() { + return position; + } + + public float getHScroll() { + return hScroll; + } + + public float getVScroll() { + return vScroll; + } + + public int getCopyKey() { + return copyKey; + } + + public boolean getPaste() { + return paste; + } + + public int getRepeat() { + return repeat; + } + + public long getSequence() { + return sequence; + } + + public int getId() { + return id; + } + + public byte[] getData() { + return data; + } + + public boolean getOn() { + return on; + } + + public int getVendorId() { + return vendorId; + } + + public int getProductId() { + return productId; + } + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/control/ControlMessageReader.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/control/ControlMessageReader.java new file mode 100644 index 0000000..b2591d6 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/control/ControlMessageReader.java @@ -0,0 +1,200 @@ +package com.genymobile.scrcpy.control; + +import com.genymobile.scrcpy.model.Position; +import com.genymobile.scrcpy.util.Binary; + +import java.io.BufferedInputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +public class ControlMessageReader { + + private static final int MESSAGE_MAX_SIZE = 1 << 18; // 256k + + public static final int CLIPBOARD_TEXT_MAX_LENGTH = MESSAGE_MAX_SIZE - 14; // type: 1 byte; sequence: 8 bytes; paste flag: 1 byte; length: 4 bytes + public static final int INJECT_TEXT_MAX_LENGTH = 300; + + private final DataInputStream dis; + + public ControlMessageReader(InputStream rawInputStream) { + dis = new DataInputStream(new BufferedInputStream(rawInputStream)); + } + + public ControlMessage read() throws IOException { + int type = dis.readUnsignedByte(); + switch (type) { + case ControlMessage.TYPE_INJECT_KEYCODE: + return parseInjectKeycode(); + case ControlMessage.TYPE_INJECT_TEXT: + return parseInjectText(); + case ControlMessage.TYPE_INJECT_TOUCH_EVENT: + return parseInjectTouchEvent(); + case ControlMessage.TYPE_INJECT_SCROLL_EVENT: + return parseInjectScrollEvent(); + case ControlMessage.TYPE_BACK_OR_SCREEN_ON: + return parseBackOrScreenOnEvent(); + case ControlMessage.TYPE_GET_CLIPBOARD: + return parseGetClipboard(); + case ControlMessage.TYPE_SET_CLIPBOARD: + return parseSetClipboard(); + case ControlMessage.TYPE_SET_DISPLAY_POWER: + return parseSetDisplayPower(); + case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL: + case ControlMessage.TYPE_EXPAND_SETTINGS_PANEL: + case ControlMessage.TYPE_COLLAPSE_PANELS: + case ControlMessage.TYPE_ROTATE_DEVICE: + case ControlMessage.TYPE_OPEN_HARD_KEYBOARD_SETTINGS: + case ControlMessage.TYPE_RESET_VIDEO: + case ControlMessage.TYPE_CAMERA_ZOOM_IN: + case ControlMessage.TYPE_CAMERA_ZOOM_OUT: + return ControlMessage.createEmpty(type); + case ControlMessage.TYPE_UHID_CREATE: + return parseUhidCreate(); + case ControlMessage.TYPE_UHID_INPUT: + return parseUhidInput(); + case ControlMessage.TYPE_UHID_DESTROY: + return parseUhidDestroy(); + case ControlMessage.TYPE_START_APP: + return parseStartApp(); + case ControlMessage.TYPE_CAMERA_SET_TORCH: + return parseCameraSetTorch(); + case ControlMessage.TYPE_RESIZE_DISPLAY: + return parseResizeDisplay(); + case ControlMessage.TYPE_SCAN_FILE: + return parseScanFile(); + default: + throw new ControlProtocolException("Unknown event type: " + type); + } + } + + private ControlMessage parseInjectKeycode() throws IOException { + int action = dis.readUnsignedByte(); + int keycode = dis.readInt(); + int repeat = dis.readInt(); + int metaState = dis.readInt(); + return ControlMessage.createInjectKeycode(action, keycode, repeat, metaState); + } + + private int parseBufferLength(int sizeBytes) throws IOException { + assert sizeBytes > 0 && sizeBytes <= 4; + int value = 0; + for (int i = 0; i < sizeBytes; ++i) { + value = (value << 8) | dis.readUnsignedByte(); + } + return value; + } + + private String parseString(int sizeBytes) throws IOException { + assert sizeBytes > 0 && sizeBytes <= 4; + byte[] data = parseByteArray(sizeBytes); + return new String(data, StandardCharsets.UTF_8); + } + + private String parseString() throws IOException { + return parseString(4); + } + + private byte[] parseByteArray(int sizeBytes) throws IOException { + int len = parseBufferLength(sizeBytes); + byte[] data = new byte[len]; + dis.readFully(data); + return data; + } + + private ControlMessage parseInjectText() throws IOException { + String text = parseString(); + return ControlMessage.createInjectText(text); + } + + private ControlMessage parseInjectTouchEvent() throws IOException { + int action = dis.readUnsignedByte(); + long pointerId = dis.readLong(); + Position position = parsePosition(); + float pressure = Binary.u16FixedPointToFloat(dis.readShort()); + int actionButton = dis.readInt(); + int buttons = dis.readInt(); + return ControlMessage.createInjectTouchEvent(action, pointerId, position, pressure, actionButton, buttons); + } + + private ControlMessage parseInjectScrollEvent() throws IOException { + Position position = parsePosition(); + // Binary.i16FixedPointToFloat() decodes values assuming the full range is [-1, 1], but the actual range is [-16, 16]. + float hScroll = Binary.i16FixedPointToFloat(dis.readShort()) * 16; + float vScroll = Binary.i16FixedPointToFloat(dis.readShort()) * 16; + int buttons = dis.readInt(); + return ControlMessage.createInjectScrollEvent(position, hScroll, vScroll, buttons); + } + + private ControlMessage parseBackOrScreenOnEvent() throws IOException { + int action = dis.readUnsignedByte(); + return ControlMessage.createBackOrScreenOn(action); + } + + private ControlMessage parseGetClipboard() throws IOException { + int copyKey = dis.readUnsignedByte(); + return ControlMessage.createGetClipboard(copyKey); + } + + private ControlMessage parseSetClipboard() throws IOException { + long sequence = dis.readLong(); + boolean paste = dis.readByte() != 0; + String text = parseString(); + return ControlMessage.createSetClipboard(sequence, text, paste); + } + + private ControlMessage parseSetDisplayPower() throws IOException { + boolean on = dis.readBoolean(); + return ControlMessage.createSetDisplayPower(on); + } + + private ControlMessage parseUhidCreate() throws IOException { + int id = dis.readUnsignedShort(); + int vendorId = dis.readUnsignedShort(); + int productId = dis.readUnsignedShort(); + String name = parseString(1); + byte[] data = parseByteArray(2); + return ControlMessage.createUhidCreate(id, vendorId, productId, name, data); + } + + private ControlMessage parseUhidInput() throws IOException { + int id = dis.readUnsignedShort(); + byte[] data = parseByteArray(2); + return ControlMessage.createUhidInput(id, data); + } + + private ControlMessage parseUhidDestroy() throws IOException { + int id = dis.readUnsignedShort(); + return ControlMessage.createUhidDestroy(id); + } + + private ControlMessage parseStartApp() throws IOException { + String name = parseString(1); + return ControlMessage.createStartApp(name); + } + + private ControlMessage parseCameraSetTorch() throws IOException { + boolean on = dis.readBoolean(); + return ControlMessage.createCameraSetTorch(on); + } + + private ControlMessage parseResizeDisplay() throws IOException { + int width = dis.readUnsignedShort(); + int height = dis.readUnsignedShort(); + return ControlMessage.createResizeDisplay(width, height); + } + + private ControlMessage parseScanFile() throws IOException { + String path = parseString(); + return ControlMessage.createScanFile(path); + } + + private Position parsePosition() throws IOException { + int x = dis.readInt(); + int y = dis.readInt(); + int screenWidth = dis.readUnsignedShort(); + int screenHeight = dis.readUnsignedShort(); + return new Position(x, y, screenWidth, screenHeight); + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/control/ControlProtocolException.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/control/ControlProtocolException.java new file mode 100644 index 0000000..cabf63e --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/control/ControlProtocolException.java @@ -0,0 +1,9 @@ +package com.genymobile.scrcpy.control; + +import java.io.IOException; + +public class ControlProtocolException extends IOException { + public ControlProtocolException(String message) { + super(message); + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/control/Controller.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/control/Controller.java new file mode 100644 index 0000000..55d6e11 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/control/Controller.java @@ -0,0 +1,893 @@ +package com.genymobile.scrcpy.control; + +import com.genymobile.scrcpy.AndroidVersions; +import com.genymobile.scrcpy.AsyncProcessor; +import com.genymobile.scrcpy.CleanUp; +import com.genymobile.scrcpy.Options; +import com.genymobile.scrcpy.device.Device; +import com.genymobile.scrcpy.display.DisplayInfo; +import com.genymobile.scrcpy.model.DeviceApp; +import com.genymobile.scrcpy.model.Point; +import com.genymobile.scrcpy.model.Position; +import com.genymobile.scrcpy.model.Size; +import com.genymobile.scrcpy.util.Ln; +import com.genymobile.scrcpy.util.LogUtils; +import com.genymobile.scrcpy.video.CameraCapture; +import com.genymobile.scrcpy.video.CaptureControl; +import com.genymobile.scrcpy.video.NewDisplayCapture; +import com.genymobile.scrcpy.video.SurfaceCapture; +import com.genymobile.scrcpy.video.VideoSource; +import com.genymobile.scrcpy.video.VirtualDisplayListener; +import com.genymobile.scrcpy.wrappers.ClipboardManager; +import com.genymobile.scrcpy.wrappers.InputManager; +import com.genymobile.scrcpy.wrappers.ServiceManager; + +import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.os.SystemClock; +import android.util.Pair; +import android.view.InputDevice; +import android.view.KeyCharacterMap; +import android.view.KeyEvent; +import android.view.MotionEvent; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +public class Controller implements AsyncProcessor, VirtualDisplayListener { + + /* + * For event injection, there are two display ids: + * - the displayId passed to the constructor (which comes from --display-id passed by the client, 0 for the main display); + * - the virtualDisplayId used for mirroring, notified by the capture instance via the VirtualDisplayListener interface. + * + * (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: + * - virtualDisplayId must be used for events relative to the display (mouse and touch events with coordinates); + * - displayId must be used for other events (like key events). + * + * If a new separate virtual display is created (using --new-display), then displayId == Device.DISPLAY_ID_NONE. In that case, all events are + * sent to the virtual display id. + */ + + private static final class DisplayData { + private final int virtualDisplayId; + private final PositionMapper positionMapper; + + private DisplayData(int virtualDisplayId, PositionMapper positionMapper) { + this.virtualDisplayId = virtualDisplayId; + this.positionMapper = positionMapper; + } + } + + private static final int DEFAULT_DEVICE_ID = 0; + + // control_msg.h values of the pointerId field in inject_touch_event message + private static final int POINTER_ID_MOUSE = -1; + + // Interval between simulated user activity events + private static final long KEEP_ACTIVE_INTERVAL_MS = 4000; + + private static final ScheduledExecutorService EXECUTOR = Executors.newSingleThreadScheduledExecutor(); + private ExecutorService startAppExecutor; + + private Thread thread; + private Thread keepActiveThread; + + private UhidManager uhidManager; + + private final boolean camera; + private final int displayId; + private final boolean supportsInputEvents; + private final ControlChannel controlChannel; + private final CleanUp cleanUp; + private final DeviceMessageSender sender; + private final boolean clipboardAutosync; + private final boolean powerOn; + private final boolean keepActive; + + private final KeyCharacterMap charMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD); + + private final AtomicBoolean isSettingClipboard = new AtomicBoolean(); + + private final AtomicReference displayData = new AtomicReference<>(); + private final Object displayDataAvailable = new Object(); // condition variable + + private long lastTouchDown; + private final PointersState pointersState = new PointersState(); + private final MotionEvent.PointerProperties[] pointerProperties = new MotionEvent.PointerProperties[PointersState.MAX_POINTERS]; + private final MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[PointersState.MAX_POINTERS]; + + private boolean keepDisplayPowerOff; + + // Used for resetting video encoding on RESET_VIDEO message or for sending camera controls + private SurfaceCapture surfaceCapture; + + public Controller(ControlChannel controlChannel, CleanUp cleanUp, Options options) { + this.camera = options.getVideoSource() == VideoSource.CAMERA; + this.controlChannel = controlChannel; + this.cleanUp = cleanUp; + + if (this.camera) { + // Unused for camera + this.displayId = Device.DISPLAY_ID_NONE; + this.supportsInputEvents = false; + this.sender = null; + this.clipboardAutosync = false; + this.powerOn = false; + this.keepActive = false; + return; + } + + this.displayId = options.getDisplayId(); + + this.clipboardAutosync = options.getClipboardAutosync(); + this.powerOn = options.getPowerOn(); + this.keepActive = options.getKeepActive(); + initPointers(); + sender = new DeviceMessageSender(controlChannel); + + supportsInputEvents = Device.supportsInputEvents(displayId); + if (!supportsInputEvents) { + Ln.w("Input events are not supported for secondary displays before Android 10"); + } + + // Make sure the clipboard manager is always created from the main thread (even if clipboardAutosync is disabled) + ClipboardManager clipboardManager = ServiceManager.getClipboardManager(); + if (clipboardAutosync) { + // If control and autosync are enabled, synchronize Android clipboard to the computer automatically + if (clipboardManager != null) { + clipboardManager.addPrimaryClipChangedListener(() -> { + if (isSettingClipboard.get()) { + // This is a notification for the change we are currently applying, ignore it + return; + } + String text = Device.getClipboardText(); + if (text != null) { + DeviceMessage msg = DeviceMessage.createClipboard(text); + sender.send(msg); + } + }); + } else { + Ln.w("No clipboard manager, copy-paste between device and computer will not work"); + } + } + } + + @Override + public void onNewVirtualDisplay(int virtualDisplayId, PositionMapper positionMapper) { + DisplayData data = new DisplayData(virtualDisplayId, positionMapper); + DisplayData old = this.displayData.getAndSet(data); + if (old == null) { + // The very first time the Controller is notified of a new virtual display + synchronized (displayDataAvailable) { + displayDataAvailable.notify(); + } + } + } + + public void setSurfaceCapture(SurfaceCapture surfaceCapture) { + this.surfaceCapture = surfaceCapture; + } + + private UhidManager getUhidManager() { + if (uhidManager == null) { + int uhidDisplayId = displayId; + if (Build.VERSION.SDK_INT >= AndroidVersions.API_35_ANDROID_15) { + if (displayId == Device.DISPLAY_ID_NONE) { + // Mirroring a new virtual display id (using --new-display-id feature) on Android >= 15, where the UHID mouse pointer can be + // associated to the virtual display + try { + // Wait for at most 1 second until a virtual display id is known + DisplayData data = waitDisplayData(1000); + if (data != null) { + uhidDisplayId = data.virtualDisplayId; + } + } catch (InterruptedException e) { + // do nothing + } + } + } + + String displayUniqueId = null; + if (uhidDisplayId > 0) { + // Ignore Device.DISPLAY_ID_NONE and 0 (main display) + DisplayInfo displayInfo = ServiceManager.getDisplayManager().getDisplayInfo(uhidDisplayId); + if (displayInfo != null) { + displayUniqueId = displayInfo.getUniqueId(); + } + } + uhidManager = new UhidManager(sender, displayUniqueId); + } + + return uhidManager; + } + + private void initPointers() { + for (int i = 0; i < PointersState.MAX_POINTERS; ++i) { + MotionEvent.PointerProperties props = new MotionEvent.PointerProperties(); + props.toolType = MotionEvent.TOOL_TYPE_FINGER; + + MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords(); + coords.orientation = 0; + coords.size = 0; + + pointerProperties[i] = props; + pointerCoords[i] = coords; + } + } + + private void control() throws IOException { + // on start, power on the device + if (!camera && powerOn && displayId == 0 && !Device.isScreenOn(displayId)) { + Device.pressReleaseKeycode(KeyEvent.KEYCODE_POWER, displayId, Device.INJECT_MODE_ASYNC); + + // dirty hack + // After POWER is injected, the device is powered on asynchronously. + // To turn the device screen off while mirroring, the client will send a message that + // would be handled before the device is actually powered on, so its effect would + // be "canceled" once the device is turned back on. + // Adding this delay prevents to handle the message before the device is actually + // powered on. + SystemClock.sleep(500); + } + + boolean alive = true; + while (!Thread.currentThread().isInterrupted() && alive) { + alive = handleEvent(); + } + } + + private void startKeepActiveThread() { + keepActiveThread = new Thread(() -> { + try { + while (true) { + Thread.sleep(KEEP_ACTIVE_INTERVAL_MS); + int actionDisplayId = getActionDisplayId(); + if (actionDisplayId != Device.DISPLAY_ID_NONE) { + Device.keepActive(actionDisplayId); + } + } + } catch (InterruptedException e) { + // ignore + } catch (Throwable e) { + Ln.e("Keep active error", e); + } finally { + Ln.d("Keep active thread stopped"); + } + }); + keepActiveThread.setName("keep-active"); + keepActiveThread.setDaemon(true); + keepActiveThread.start(); + } + + @Override + public void start(TerminationListener listener) { + if (keepActive) { + startKeepActiveThread(); + } + + thread = new Thread(() -> { + try { + control(); + } catch (IOException e) { + Ln.e("Controller error", e); + } finally { + Ln.d("Controller stopped"); + if (uhidManager != null) { + uhidManager.closeAll(); + } + listener.onTerminated(true); + } + }, "control-recv"); + thread.start(); + if (sender != null) { + sender.start(); + } + } + + @Override + public void stop() { + if (keepActiveThread != null) { + keepActiveThread.interrupt(); + } + if (thread != null) { + thread.interrupt(); + } + if (sender != null) { + sender.stop(); + } + } + + @Override + public void join() throws InterruptedException { + if (thread != null) { + thread.join(); + } + if (sender != null) { + sender.join(); + } + } + + private boolean handleEvent() throws IOException { + ControlMessage msg; + try { + msg = controlChannel.recv(); + } catch (ControlProtocolException e) { + Ln.e("Control protocol error", e); + return false; + } catch (IOException e) { + // this is expected on close + return false; + } + + int type = msg.getType(); + + // Events for all sources (display or camera) + switch (type) { + case ControlMessage.TYPE_RESET_VIDEO: + resetVideo(); + return true; + default: + // fall through + } + + if (!camera) { + switch (type) { + case ControlMessage.TYPE_INJECT_KEYCODE: + if (supportsInputEvents) { + injectKeycode(msg.getAction(), msg.getKeycode(), msg.getRepeat(), msg.getMetaState()); + } + return true; + case ControlMessage.TYPE_INJECT_TEXT: + if (supportsInputEvents) { + injectText(msg.getText()); + } + return true; + case ControlMessage.TYPE_INJECT_TOUCH_EVENT: + if (supportsInputEvents) { + injectTouch( + msg.getAction(), msg.getPointerId(), msg.getPosition(), msg.getPressure(), msg.getActionButton(), msg.getButtons()); + } + return true; + case ControlMessage.TYPE_INJECT_SCROLL_EVENT: + if (supportsInputEvents) { + injectScroll(msg.getPosition(), msg.getHScroll(), msg.getVScroll(), msg.getButtons()); + } + return true; + case ControlMessage.TYPE_BACK_OR_SCREEN_ON: + if (supportsInputEvents) { + pressBackOrTurnScreenOn(msg.getAction()); + } + return true; + case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL: + Device.expandNotificationPanel(); + return true; + case ControlMessage.TYPE_EXPAND_SETTINGS_PANEL: + Device.expandSettingsPanel(); + return true; + case ControlMessage.TYPE_COLLAPSE_PANELS: + Device.collapsePanels(); + return true; + case ControlMessage.TYPE_GET_CLIPBOARD: + getClipboard(msg.getCopyKey()); + return true; + case ControlMessage.TYPE_SET_CLIPBOARD: + setClipboard(msg.getText(), msg.getPaste(), msg.getSequence()); + return true; + case ControlMessage.TYPE_SET_DISPLAY_POWER: + if (supportsInputEvents) { + setDisplayPower(msg.getOn()); + } + return true; + case ControlMessage.TYPE_ROTATE_DEVICE: + int actionDisplayId = getActionDisplayId(); + if (actionDisplayId != Device.DISPLAY_ID_NONE) { + Device.rotateDevice(actionDisplayId); + } + return true; + case ControlMessage.TYPE_UHID_CREATE: + getUhidManager().open(msg.getId(), msg.getVendorId(), msg.getProductId(), msg.getText(), msg.getData()); + return true; + case ControlMessage.TYPE_UHID_INPUT: + getUhidManager().writeInput(msg.getId(), msg.getData()); + return true; + case ControlMessage.TYPE_UHID_DESTROY: + getUhidManager().close(msg.getId()); + return true; + case ControlMessage.TYPE_OPEN_HARD_KEYBOARD_SETTINGS: + openHardKeyboardSettings(); + return true; + case ControlMessage.TYPE_START_APP: + startAppAsync(msg.getText()); + return true; + case ControlMessage.TYPE_RESIZE_DISPLAY: + resizeDisplay(msg.getWidth(), msg.getHeight()); + return true; + case ControlMessage.TYPE_SCAN_FILE: + scanFile(msg.getText()); + return true; + default: + // fall through + } + } else { + assert surfaceCapture instanceof CameraCapture; + CameraCapture cameraCapture = (CameraCapture) surfaceCapture; + switch (type) { + case ControlMessage.TYPE_CAMERA_SET_TORCH: + cameraCapture.setTorchEnabled(msg.getOn()); + return true; + case ControlMessage.TYPE_CAMERA_ZOOM_IN: + cameraCapture.zoomIn(); + return true; + case ControlMessage.TYPE_CAMERA_ZOOM_OUT: + cameraCapture.zoomOut(); + return true; + default: + // fall through + } + } + + throw new AssertionError("Unexpected message type: " + type); + } + + private boolean injectKeycode(int action, int keycode, int repeat, int metaState) { + if (keepDisplayPowerOff && action == KeyEvent.ACTION_UP && (keycode == KeyEvent.KEYCODE_POWER || keycode == KeyEvent.KEYCODE_WAKEUP)) { + assert displayId != Device.DISPLAY_ID_NONE; + scheduleDisplayPowerOff(displayId); + } + return injectKeyEvent(action, keycode, repeat, metaState, Device.INJECT_MODE_ASYNC); + } + + private boolean injectChar(char c) { + String decomposed = KeyComposition.decompose(c); + char[] chars = decomposed != null ? decomposed.toCharArray() : new char[]{c}; + KeyEvent[] events = charMap.getEvents(chars); + if (events == null) { + return false; + } + + int actionDisplayId = getActionDisplayId(); + if (actionDisplayId != Device.DISPLAY_ID_NONE) { + for (KeyEvent event : events) { + if (!Device.injectEvent(event, actionDisplayId, Device.INJECT_MODE_ASYNC)) { + return false; + } + } + } + return true; + } + + private int injectText(String text) { + int successCount = 0; + for (char c : text.toCharArray()) { + if (!injectChar(c)) { + Ln.w("Could not inject char u+" + String.format("%04x", (int) c)); + continue; + } + successCount++; + } + return successCount; + } + + private Pair getEventPointAndDisplayId(Position position) { + // it hides the field on purpose, to read it with atomic access + @SuppressWarnings("checkstyle:HiddenField") + DisplayData displayData = this.displayData.get(); + // In scrcpy, displayData should never be null (a touch event can only be generated from the client when a video frame is present). + // However, it is possible to send events without video playback when using scrcpy-server alone (except for virtual displays). + assert displayData != null || displayId != Device.DISPLAY_ID_NONE : "Cannot receive a positional event without a display"; + + Point point; + int targetDisplayId; + if (displayData != null) { + point = displayData.positionMapper.map(position); + if (point == null) { + if (Ln.isEnabled(Ln.Level.VERBOSE)) { + Size eventSize = position.getScreenSize(); + Size currentSize = displayData.positionMapper.getVideoSize(); + Ln.v("Ignore positional event generated for size " + eventSize + " (current size is " + currentSize + ")"); + } + return null; + } + targetDisplayId = displayData.virtualDisplayId; + } else { + // No display, use the raw coordinates + point = position.getPoint(); + targetDisplayId = displayId; + } + + return Pair.create(point, targetDisplayId); + } + + private boolean injectTouch(int action, long pointerId, Position position, float pressure, int actionButton, int buttons) { + long now = SystemClock.uptimeMillis(); + + Pair pair = getEventPointAndDisplayId(position); + if (pair == null) { + return false; + } + + Point point = pair.first; + int targetDisplayId = pair.second; + + int pointerIndex = pointersState.getPointerIndex(pointerId); + if (pointerIndex == -1) { + Ln.w("Too many pointers for touch event"); + return false; + } + Pointer pointer = pointersState.get(pointerIndex); + pointer.setPoint(point); + pointer.setPressure(pressure); + + int source; + boolean activeSecondaryButtons = ((actionButton | buttons) & ~MotionEvent.BUTTON_PRIMARY) != 0; + if (pointerId == POINTER_ID_MOUSE && (action == MotionEvent.ACTION_HOVER_MOVE || activeSecondaryButtons)) { + // real mouse event, or event incompatible with a finger + pointerProperties[pointerIndex].toolType = MotionEvent.TOOL_TYPE_MOUSE; + source = InputDevice.SOURCE_MOUSE; + pointer.setUp(buttons == 0); + } else { + // POINTER_ID_GENERIC_FINGER, POINTER_ID_VIRTUAL_FINGER or real touch from device + pointerProperties[pointerIndex].toolType = MotionEvent.TOOL_TYPE_FINGER; + source = InputDevice.SOURCE_TOUCHSCREEN; + // Buttons must not be set for touch events + buttons = 0; + pointer.setUp(action == MotionEvent.ACTION_UP); + } + + int pointerCount = pointersState.update(pointerProperties, pointerCoords); + if (pointerCount == 1) { + if (action == MotionEvent.ACTION_DOWN) { + lastTouchDown = now; + } + } else { + // secondary pointers must use ACTION_POINTER_* ORed with the pointerIndex + if (action == MotionEvent.ACTION_UP) { + action = MotionEvent.ACTION_POINTER_UP | (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT); + } else if (action == MotionEvent.ACTION_DOWN) { + action = MotionEvent.ACTION_POINTER_DOWN | (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT); + } + } + + /* If the input device is a mouse (on API >= 23): + * - the first button pressed must first generate ACTION_DOWN; + * - all button pressed (including the first one) must generate ACTION_BUTTON_PRESS; + * - all button released (including the last one) must generate ACTION_BUTTON_RELEASE; + * - the last button released must in addition generate ACTION_UP. + * + * Otherwise, Chrome does not work properly: + */ + if (Build.VERSION.SDK_INT >= AndroidVersions.API_23_ANDROID_6_0 && source == InputDevice.SOURCE_MOUSE) { + if (action == MotionEvent.ACTION_DOWN) { + if (actionButton == buttons) { + // First button pressed: ACTION_DOWN + MotionEvent downEvent = MotionEvent.obtain(lastTouchDown, now, MotionEvent.ACTION_DOWN, pointerCount, pointerProperties, + pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, source, 0); + if (!Device.injectEvent(downEvent, targetDisplayId, Device.INJECT_MODE_ASYNC)) { + return false; + } + } + + // Any button pressed: ACTION_BUTTON_PRESS + MotionEvent pressEvent = MotionEvent.obtain(lastTouchDown, now, MotionEvent.ACTION_BUTTON_PRESS, pointerCount, pointerProperties, + pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, source, 0); + if (!InputManager.setActionButton(pressEvent, actionButton)) { + return false; + } + if (!Device.injectEvent(pressEvent, targetDisplayId, Device.INJECT_MODE_ASYNC)) { + return false; + } + + return true; + } + + if (action == MotionEvent.ACTION_UP) { + // Any button released: ACTION_BUTTON_RELEASE + MotionEvent releaseEvent = MotionEvent.obtain(lastTouchDown, now, MotionEvent.ACTION_BUTTON_RELEASE, pointerCount, pointerProperties, + pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, source, 0); + if (!InputManager.setActionButton(releaseEvent, actionButton)) { + return false; + } + if (!Device.injectEvent(releaseEvent, targetDisplayId, Device.INJECT_MODE_ASYNC)) { + return false; + } + + if (buttons == 0) { + // Last button released: ACTION_UP + MotionEvent upEvent = MotionEvent.obtain(lastTouchDown, now, MotionEvent.ACTION_UP, pointerCount, pointerProperties, + pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, source, 0); + if (!Device.injectEvent(upEvent, targetDisplayId, Device.INJECT_MODE_ASYNC)) { + return false; + } + } + + return true; + } + } + + MotionEvent event = MotionEvent.obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, + DEFAULT_DEVICE_ID, 0, source, 0); + return Device.injectEvent(event, targetDisplayId, Device.INJECT_MODE_ASYNC); + } + + private boolean injectScroll(Position position, float hScroll, float vScroll, int buttons) { + long now = SystemClock.uptimeMillis(); + + Pair pair = getEventPointAndDisplayId(position); + if (pair == null) { + return false; + } + + Point point = pair.first; + int targetDisplayId = pair.second; + + MotionEvent.PointerProperties props = pointerProperties[0]; + props.id = 0; + + MotionEvent.PointerCoords coords = pointerCoords[0]; + coords.x = point.getX(); + coords.y = point.getY(); + coords.setAxisValue(MotionEvent.AXIS_HSCROLL, hScroll); + coords.setAxisValue(MotionEvent.AXIS_VSCROLL, vScroll); + + MotionEvent event = MotionEvent.obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, + DEFAULT_DEVICE_ID, 0, InputDevice.SOURCE_MOUSE, 0); + return Device.injectEvent(event, targetDisplayId, Device.INJECT_MODE_ASYNC); + } + + /** + * Schedule a call to set display power to off after a small delay. + */ + private static void scheduleDisplayPowerOff(int displayId) { + EXECUTOR.schedule(() -> { + Ln.i("Forcing display off"); + Device.setDisplayPower(displayId, false); + }, 200, TimeUnit.MILLISECONDS); + } + + private boolean pressBackOrTurnScreenOn(int action) { + boolean injectBack; + // Device.isScreenOn(displayId) ignores the displayId below Android 14 + if (Build.VERSION.SDK_INT >= AndroidVersions.API_34_ANDROID_14) { + // Inject BACK if the screen is on for the current virtual display id + int actionDisplayId = getActionDisplayId(); + injectBack = actionDisplayId == Device.DISPLAY_ID_NONE || Device.isScreenOn(actionDisplayId); + } else { + // Inject BACK if the display is not the main display, or if the main display is on + injectBack = displayId != 0 || Device.isScreenOn(0); + } + if (injectBack) { + return injectKeyEvent(action, KeyEvent.KEYCODE_BACK, 0, 0, Device.INJECT_MODE_ASYNC); + } + + // Screen is off + // Only press POWER on ACTION_DOWN + if (action != KeyEvent.ACTION_DOWN) { + // do nothing + return true; + } + + if (keepDisplayPowerOff) { + assert displayId != Device.DISPLAY_ID_NONE; + scheduleDisplayPowerOff(displayId); + } + return pressReleaseKeycode(KeyEvent.KEYCODE_POWER, Device.INJECT_MODE_ASYNC); + } + + private void getClipboard(int copyKey) { + // On Android >= 7, press the COPY or CUT key if requested + if (copyKey != ControlMessage.COPY_KEY_NONE && Build.VERSION.SDK_INT >= AndroidVersions.API_24_ANDROID_7_0 && supportsInputEvents) { + int key = copyKey == ControlMessage.COPY_KEY_COPY ? KeyEvent.KEYCODE_COPY : KeyEvent.KEYCODE_CUT; + // Wait until the event is finished, to ensure that the clipboard text we read just after is the correct one + pressReleaseKeycode(key, Device.INJECT_MODE_WAIT_FOR_FINISH); + } + + // If clipboard autosync is enabled, then the device clipboard is synchronized to the computer clipboard whenever it changes, in + // particular when COPY or CUT are injected, so it should not be synchronized twice. On Android < 7, do not synchronize at all rather than + // copying an old clipboard content. + if (!clipboardAutosync) { + String clipboardText = Device.getClipboardText(); + if (clipboardText != null) { + DeviceMessage msg = DeviceMessage.createClipboard(clipboardText); + sender.send(msg); + } + } + } + + private boolean setClipboard(String text, boolean paste, long sequence) { + isSettingClipboard.set(true); + boolean ok = Device.setClipboardText(text); + isSettingClipboard.set(false); + if (ok) { + Ln.i("Device clipboard set"); + } + + // On Android >= 7, also press the PASTE key if requested + if (paste && Build.VERSION.SDK_INT >= AndroidVersions.API_24_ANDROID_7_0 && supportsInputEvents) { + pressReleaseKeycode(KeyEvent.KEYCODE_PASTE, Device.INJECT_MODE_ASYNC); + } + + if (sequence != ControlMessage.SEQUENCE_INVALID) { + // Acknowledgement requested + DeviceMessage msg = DeviceMessage.createAckClipboard(sequence); + sender.send(msg); + } + + return ok; + } + + private void openHardKeyboardSettings() { + Intent intent = new Intent("android.settings.HARD_KEYBOARD_SETTINGS"); + ServiceManager.getActivityManager().startActivity(intent); + } + + private boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState, int injectMode) { + int actionDisplayId = getActionDisplayId(); + if (actionDisplayId == Device.DISPLAY_ID_NONE) { + return false; + } + return Device.injectKeyEvent(action, keyCode, repeat, metaState, actionDisplayId, injectMode); + } + + private boolean pressReleaseKeycode(int keyCode, int injectMode) { + int actionDisplayId = getActionDisplayId(); + if (actionDisplayId == Device.DISPLAY_ID_NONE) { + return false; + } + return Device.pressReleaseKeycode(keyCode, actionDisplayId, injectMode); + } + + private int getActionDisplayId() { + if (displayId != Device.DISPLAY_ID_NONE) { + // Real screen mirrored, use the source display id + return displayId; + } + + // Virtual display created by --new-display, use the virtualDisplayId + DisplayData data = displayData.get(); + if (data == null) { + return Device.DISPLAY_ID_NONE; + } + + return data.virtualDisplayId; + } + + private void startAppAsync(String name) { + if (startAppExecutor == null) { + startAppExecutor = Executors.newSingleThreadExecutor(); + } + + // Listing and selecting the app may take a lot of time + startAppExecutor.submit(() -> startApp(name)); + } + + private void startApp(String name) { + boolean forceStopBeforeStart = name.startsWith("+"); + if (forceStopBeforeStart) { + name = name.substring(1); + } + + 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(); + if (startAppDisplayId == Device.DISPLAY_ID_NONE) { + Ln.e("No known display id to start app \"" + name + "\""); + return; + } + + Ln.i("Starting app \"" + app.getName() + "\" [" + app.getPackageName() + "] on display " + startAppDisplayId + "..."); + Device.startApp(app.getPackageName(), startAppDisplayId, forceStopBeforeStart); + } + + private int getStartAppDisplayId() { + if (displayId != Device.DISPLAY_ID_NONE) { + return displayId; + } + + // Mirroring a new virtual display id (using --new-display-id feature) + try { + // Wait for at most 1 second until a virtual display id is known + DisplayData data = waitDisplayData(1000); + if (data != null) { + return data.virtualDisplayId; + } + } catch (InterruptedException e) { + // do nothing + } + + // No display id available + return Device.DISPLAY_ID_NONE; + } + + private DisplayData waitDisplayData(long timeoutMillis) throws InterruptedException { + long deadline = System.currentTimeMillis() + timeoutMillis; + + synchronized (displayDataAvailable) { + DisplayData data = displayData.get(); + while (data == null) { + long timeout = deadline - System.currentTimeMillis(); + if (timeout < 0) { + return null; + } + if (timeout > 0) { + displayDataAvailable.wait(timeout); + } + data = displayData.get(); + } + + return data; + } + } + + private void setDisplayPower(boolean on) { + // Change the power of the main display when mirroring a virtual display + int targetDisplayId = displayId != Device.DISPLAY_ID_NONE ? displayId : 0; + boolean setDisplayPowerOk = Device.setDisplayPower(targetDisplayId, on); + if (setDisplayPowerOk) { + // Do not keep display power off for virtual displays: MOD+p must wake up the physical device + keepDisplayPowerOff = displayId != Device.DISPLAY_ID_NONE && !on; + Ln.i("Device display turned " + (on ? "on" : "off")); + if (cleanUp != null) { + boolean mustRestoreOnExit = !on; + cleanUp.setRestoreDisplayPower(mustRestoreOnExit); + } + } + } + + private void resetVideo() { + if (surfaceCapture != null) { + Ln.i("Video capture reset"); + surfaceCapture.getCaptureControl().reset(CaptureControl.RESET_REASON_CLIENT_RESET); + } + } + + private void resizeDisplay(int width, int height) { + NewDisplayCapture newDisplayCapture = (NewDisplayCapture) surfaceCapture; + newDisplayCapture.requestResize(width, height); + } + + private void scanFile(String path) { + try { + @SuppressWarnings("deprecation") + Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(new File(path))); + Device.sendBroadcast(intent); + } catch (Throwable t) { + Ln.e("MediaStore scan failed for " + path, t); + } + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/control/DeviceMessage.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/control/DeviceMessage.java new file mode 100644 index 0000000..079a7a0 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/control/DeviceMessage.java @@ -0,0 +1,59 @@ +package com.genymobile.scrcpy.control; + +public final class DeviceMessage { + + public static final int TYPE_CLIPBOARD = 0; + public static final int TYPE_ACK_CLIPBOARD = 1; + public static final int TYPE_UHID_OUTPUT = 2; + + private int type; + private String text; + private long sequence; + private int id; + private byte[] data; + + private DeviceMessage() { + } + + public static DeviceMessage createClipboard(String text) { + DeviceMessage event = new DeviceMessage(); + event.type = TYPE_CLIPBOARD; + event.text = text; + return event; + } + + public static DeviceMessage createAckClipboard(long sequence) { + DeviceMessage event = new DeviceMessage(); + event.type = TYPE_ACK_CLIPBOARD; + event.sequence = sequence; + return event; + } + + public static DeviceMessage createUhidOutput(int id, byte[] data) { + DeviceMessage event = new DeviceMessage(); + event.type = TYPE_UHID_OUTPUT; + event.id = id; + event.data = data; + return event; + } + + public int getType() { + return type; + } + + public String getText() { + return text; + } + + public long getSequence() { + return sequence; + } + + public int getId() { + return id; + } + + public byte[] getData() { + return data; + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/control/DeviceMessageSender.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/control/DeviceMessageSender.java new file mode 100644 index 0000000..dc5e6be --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/control/DeviceMessageSender.java @@ -0,0 +1,57 @@ +package com.genymobile.scrcpy.control; + +import com.genymobile.scrcpy.util.Ln; + +import java.io.IOException; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; + +public final class DeviceMessageSender { + + private final ControlChannel controlChannel; + + private Thread thread; + private final BlockingQueue queue = new ArrayBlockingQueue<>(16); + + public DeviceMessageSender(ControlChannel controlChannel) { + this.controlChannel = controlChannel; + } + + public void send(DeviceMessage msg) { + if (!queue.offer(msg)) { + Ln.w("Device message dropped: " + msg.getType()); + } + } + + private void loop() throws IOException, InterruptedException { + while (!Thread.currentThread().isInterrupted()) { + DeviceMessage msg = queue.take(); + controlChannel.send(msg); + } + } + + public void start() { + thread = new Thread(() -> { + try { + loop(); + } catch (IOException | InterruptedException e) { + // this is expected on close + } finally { + Ln.d("Device message sender stopped"); + } + }, "control-send"); + thread.start(); + } + + public void stop() { + if (thread != null) { + thread.interrupt(); + } + } + + public void join() throws InterruptedException { + if (thread != null) { + thread.join(); + } + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/control/DeviceMessageWriter.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/control/DeviceMessageWriter.java new file mode 100644 index 0000000..a18a2e5 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/control/DeviceMessageWriter.java @@ -0,0 +1,47 @@ +package com.genymobile.scrcpy.control; + +import com.genymobile.scrcpy.util.StringUtils; + +import java.io.BufferedOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; + +public class DeviceMessageWriter { + + private static final int MESSAGE_MAX_SIZE = 1 << 18; // 256k + public static final int CLIPBOARD_TEXT_MAX_LENGTH = MESSAGE_MAX_SIZE - 5; // type: 1 byte; length: 4 bytes + + private final DataOutputStream dos; + + public DeviceMessageWriter(OutputStream rawOutputStream) { + dos = new DataOutputStream(new BufferedOutputStream(rawOutputStream)); + } + + public void write(DeviceMessage msg) throws IOException { + int type = msg.getType(); + dos.writeByte(type); + switch (type) { + case DeviceMessage.TYPE_CLIPBOARD: + String text = msg.getText(); + byte[] raw = text.getBytes(StandardCharsets.UTF_8); + int len = StringUtils.getUtf8TruncationIndex(raw, CLIPBOARD_TEXT_MAX_LENGTH); + dos.writeInt(len); + dos.write(raw, 0, len); + break; + case DeviceMessage.TYPE_ACK_CLIPBOARD: + dos.writeLong(msg.getSequence()); + break; + case DeviceMessage.TYPE_UHID_OUTPUT: + dos.writeShort(msg.getId()); + byte[] data = msg.getData(); + dos.writeShort(data.length); + dos.write(data); + break; + default: + throw new ControlProtocolException("Unknown event type: " + type); + } + dos.flush(); + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/control/KeyComposition.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/control/KeyComposition.java new file mode 100644 index 0000000..5b988f5 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/control/KeyComposition.java @@ -0,0 +1,174 @@ +package com.genymobile.scrcpy.control; + +import java.util.HashMap; +import java.util.Map; + +/** + * Decompose accented characters. + *

+ * For example, {@link #decompose(char) decompose('é')} returns {@code "\u0301e"}. + *

+ * This is useful for injecting key events to generate the expected character ({@link android.view.KeyCharacterMap#getEvents(char[])} + * KeyCharacterMap.getEvents()} returns {@code null} with input {@code "é"} but works with input {@code "\u0301e"}). + *

+ * See diacritical dead key characters. + */ +public final class KeyComposition { + + private static final String KEY_DEAD_GRAVE = "\u0300"; + private static final String KEY_DEAD_ACUTE = "\u0301"; + private static final String KEY_DEAD_CIRCUMFLEX = "\u0302"; + private static final String KEY_DEAD_TILDE = "\u0303"; + private static final String KEY_DEAD_UMLAUT = "\u0308"; + + private static final Map COMPOSITION_MAP = createDecompositionMap(); + + private KeyComposition() { + // not instantiable + } + + public static String decompose(char c) { + return COMPOSITION_MAP.get(c); + } + + private static String grave(char c) { + return KEY_DEAD_GRAVE + c; + } + + private static String acute(char c) { + return KEY_DEAD_ACUTE + c; + } + + private static String circumflex(char c) { + return KEY_DEAD_CIRCUMFLEX + c; + } + + private static String tilde(char c) { + return KEY_DEAD_TILDE + c; + } + + private static String umlaut(char c) { + return KEY_DEAD_UMLAUT + c; + } + + private static Map createDecompositionMap() { + Map map = new HashMap<>(); + map.put('À', grave('A')); + map.put('È', grave('E')); + map.put('Ì', grave('I')); + map.put('Ò', grave('O')); + map.put('Ù', grave('U')); + map.put('à', grave('a')); + map.put('è', grave('e')); + map.put('ì', grave('i')); + map.put('ò', grave('o')); + map.put('ù', grave('u')); + map.put('Ǹ', grave('N')); + map.put('ǹ', grave('n')); + map.put('Ẁ', grave('W')); + map.put('ẁ', grave('w')); + map.put('Ỳ', grave('Y')); + map.put('ỳ', grave('y')); + + map.put('Á', acute('A')); + map.put('É', acute('E')); + map.put('Í', acute('I')); + map.put('Ó', acute('O')); + map.put('Ú', acute('U')); + map.put('Ý', acute('Y')); + map.put('á', acute('a')); + map.put('é', acute('e')); + map.put('í', acute('i')); + map.put('ó', acute('o')); + map.put('ú', acute('u')); + map.put('ý', acute('y')); + map.put('Ć', acute('C')); + map.put('ć', acute('c')); + map.put('Ĺ', acute('L')); + map.put('ĺ', acute('l')); + map.put('Ń', acute('N')); + map.put('ń', acute('n')); + map.put('Ŕ', acute('R')); + map.put('ŕ', acute('r')); + map.put('Ś', acute('S')); + map.put('ś', acute('s')); + map.put('Ź', acute('Z')); + map.put('ź', acute('z')); + map.put('Ǵ', acute('G')); + map.put('ǵ', acute('g')); + map.put('Ḉ', acute('Ç')); + map.put('ḉ', acute('ç')); + map.put('Ḱ', acute('K')); + map.put('ḱ', acute('k')); + map.put('Ḿ', acute('M')); + map.put('ḿ', acute('m')); + map.put('Ṕ', acute('P')); + map.put('ṕ', acute('p')); + map.put('Ẃ', acute('W')); + map.put('ẃ', acute('w')); + + map.put('Â', circumflex('A')); + map.put('Ê', circumflex('E')); + map.put('Î', circumflex('I')); + map.put('Ô', circumflex('O')); + map.put('Û', circumflex('U')); + map.put('â', circumflex('a')); + map.put('ê', circumflex('e')); + map.put('î', circumflex('i')); + map.put('ô', circumflex('o')); + map.put('û', circumflex('u')); + map.put('Ĉ', circumflex('C')); + map.put('ĉ', circumflex('c')); + map.put('Ĝ', circumflex('G')); + map.put('ĝ', circumflex('g')); + map.put('Ĥ', circumflex('H')); + map.put('ĥ', circumflex('h')); + map.put('Ĵ', circumflex('J')); + map.put('ĵ', circumflex('j')); + map.put('Ŝ', circumflex('S')); + map.put('ŝ', circumflex('s')); + map.put('Ŵ', circumflex('W')); + map.put('ŵ', circumflex('w')); + map.put('Ŷ', circumflex('Y')); + map.put('ŷ', circumflex('y')); + map.put('Ẑ', circumflex('Z')); + map.put('ẑ', circumflex('z')); + + map.put('Ã', tilde('A')); + map.put('Ñ', tilde('N')); + map.put('Õ', tilde('O')); + map.put('ã', tilde('a')); + map.put('ñ', tilde('n')); + map.put('õ', tilde('o')); + map.put('Ĩ', tilde('I')); + map.put('ĩ', tilde('i')); + map.put('Ũ', tilde('U')); + map.put('ũ', tilde('u')); + map.put('Ẽ', tilde('E')); + map.put('ẽ', tilde('e')); + map.put('Ỹ', tilde('Y')); + map.put('ỹ', tilde('y')); + + map.put('Ä', umlaut('A')); + map.put('Ë', umlaut('E')); + map.put('Ï', umlaut('I')); + map.put('Ö', umlaut('O')); + map.put('Ü', umlaut('U')); + map.put('ä', umlaut('a')); + map.put('ë', umlaut('e')); + map.put('ï', umlaut('i')); + map.put('ö', umlaut('o')); + map.put('ü', umlaut('u')); + map.put('ÿ', umlaut('y')); + map.put('Ÿ', umlaut('Y')); + map.put('Ḧ', umlaut('H')); + map.put('ḧ', umlaut('h')); + map.put('Ẅ', umlaut('W')); + map.put('ẅ', umlaut('w')); + map.put('Ẍ', umlaut('X')); + map.put('ẍ', umlaut('x')); + map.put('ẗ', umlaut('t')); + + return map; + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/control/Pointer.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/control/Pointer.java new file mode 100644 index 0000000..4624a15 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/control/Pointer.java @@ -0,0 +1,57 @@ +package com.genymobile.scrcpy.control; + +import com.genymobile.scrcpy.model.Point; + +public class Pointer { + + /** + * Pointer id as received from the client. + */ + private final long id; + + /** + * Local pointer id, using the lowest possible values to fill the {@link android.view.MotionEvent.PointerProperties PointerProperties}. + */ + private final int localId; + + private Point point; + private float pressure; + private boolean up; + + public Pointer(long id, int localId) { + this.id = id; + this.localId = localId; + } + + public long getId() { + return id; + } + + public int getLocalId() { + return localId; + } + + public Point getPoint() { + return point; + } + + public void setPoint(Point point) { + this.point = point; + } + + public float getPressure() { + return pressure; + } + + public void setPressure(float pressure) { + this.pressure = pressure; + } + + public boolean isUp() { + return up; + } + + public void setUp(boolean up) { + this.up = up; + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/control/PointersState.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/control/PointersState.java new file mode 100644 index 0000000..efe16f0 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/control/PointersState.java @@ -0,0 +1,105 @@ +package com.genymobile.scrcpy.control; + +import com.genymobile.scrcpy.model.Point; + +import android.view.MotionEvent; + +import java.util.ArrayList; +import java.util.List; + +public class PointersState { + + public static final int MAX_POINTERS = 10; + + private final List pointers = new ArrayList<>(); + + private int indexOf(long id) { + for (int i = 0; i < pointers.size(); ++i) { + Pointer pointer = pointers.get(i); + if (pointer.getId() == id) { + return i; + } + } + return -1; + } + + private boolean isLocalIdAvailable(int localId) { + for (int i = 0; i < pointers.size(); ++i) { + Pointer pointer = pointers.get(i); + if (pointer.getLocalId() == localId) { + return false; + } + } + return true; + } + + private int nextUnusedLocalId() { + for (int localId = 0; localId < MAX_POINTERS; ++localId) { + if (isLocalIdAvailable(localId)) { + return localId; + } + } + return -1; + } + + public Pointer get(int index) { + return pointers.get(index); + } + + public int getPointerIndex(long id) { + int index = indexOf(id); + if (index != -1) { + // already exists, return it + return index; + } + if (pointers.size() >= MAX_POINTERS) { + // it's full + return -1; + } + // id 0 is reserved for mouse events + int localId = nextUnusedLocalId(); + if (localId == -1) { + throw new AssertionError("pointers.size() < maxFingers implies that a local id is available"); + } + Pointer pointer = new Pointer(id, localId); + pointers.add(pointer); + // return the index of the pointer + return pointers.size() - 1; + } + + /** + * Initialize the motion event parameters. + * + * @param props the pointer properties + * @param coords the pointer coordinates + * @return The number of items initialized (the number of pointers). + */ + public int update(MotionEvent.PointerProperties[] props, MotionEvent.PointerCoords[] coords) { + int count = pointers.size(); + for (int i = 0; i < count; ++i) { + Pointer pointer = pointers.get(i); + + // id 0 is reserved for mouse events + props[i].id = pointer.getLocalId(); + + Point point = pointer.getPoint(); + coords[i].x = point.getX(); + coords[i].y = point.getY(); + coords[i].pressure = pointer.getPressure(); + } + cleanUp(); + return count; + } + + /** + * Remove all pointers which are UP. + */ + private void cleanUp() { + for (int i = pointers.size() - 1; i >= 0; --i) { + Pointer pointer = pointers.get(i); + if (pointer.isUp()) { + pointers.remove(i); + } + } + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/control/PositionMapper.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/control/PositionMapper.java new file mode 100644 index 0000000..e8daed9 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/control/PositionMapper.java @@ -0,0 +1,48 @@ +package com.genymobile.scrcpy.control; + +import com.genymobile.scrcpy.model.Point; +import com.genymobile.scrcpy.model.Position; +import com.genymobile.scrcpy.model.Size; +import com.genymobile.scrcpy.util.AffineMatrix; + +public final class PositionMapper { + + private final Size videoSize; + private final AffineMatrix videoToDeviceMatrix; + + public PositionMapper(Size videoSize, AffineMatrix videoToDeviceMatrix) { + this.videoSize = videoSize; + this.videoToDeviceMatrix = videoToDeviceMatrix; + } + + public static PositionMapper create(Size videoSize, AffineMatrix filterTransform, Size targetSize) { + boolean convertToPixels = !videoSize.equals(targetSize) || filterTransform != null; + AffineMatrix transform = filterTransform; + if (convertToPixels) { + AffineMatrix inputTransform = AffineMatrix.ndcFromPixels(videoSize); + AffineMatrix outputTransform = AffineMatrix.ndcToPixels(targetSize); + transform = outputTransform.multiply(transform).multiply(inputTransform); + } + + return new PositionMapper(videoSize, transform); + } + + public Size getVideoSize() { + return videoSize; + } + + public Point map(Position position) { + Size clientVideoSize = position.getScreenSize(); + if (!videoSize.equals(clientVideoSize)) { + // The client sends a click relative to a video with wrong dimensions, + // the device may have been rotated since the event was generated, so ignore the event + return null; + } + + Point point = position.getPoint(); + if (videoToDeviceMatrix != null) { + point = videoToDeviceMatrix.apply(point); + } + return point; + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/control/UhidManager.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/control/UhidManager.java new file mode 100644 index 0000000..20532c0 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/control/UhidManager.java @@ -0,0 +1,287 @@ +package com.genymobile.scrcpy.control; + +import com.genymobile.scrcpy.AndroidVersions; +import com.genymobile.scrcpy.util.Ln; +import com.genymobile.scrcpy.util.StringUtils; +import com.genymobile.scrcpy.wrappers.ServiceManager; + +import android.os.Build; +import android.os.HandlerThread; +import android.os.MessageQueue; +import android.system.ErrnoException; +import android.system.Os; +import android.system.OsConstants; +import android.util.ArrayMap; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.InterruptedIOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; + +public final class UhidManager { + + // Linux: include/uapi/linux/uhid.h + private static final int UHID_OUTPUT = 6; + private static final int UHID_CREATE2 = 11; + private static final int UHID_INPUT2 = 12; + + // Linux: include/uapi/linux/input.h + private static final short BUS_VIRTUAL = 0x06; + + private static final int SIZE_OF_UHID_EVENT = 4380; // sizeof(struct uhid_event) + + // Must be unique across the system + private static final String INPUT_PORT = "scrcpy:" + Os.getpid(); + + private final String displayUniqueId; + + private final ArrayMap fds = new ArrayMap<>(); + private final ByteBuffer buffer = ByteBuffer.allocate(SIZE_OF_UHID_EVENT).order(ByteOrder.nativeOrder()); + + private final DeviceMessageSender sender; + private final MessageQueue queue; + + public UhidManager(DeviceMessageSender sender, String displayUniqueId) { + this.sender = sender; + this.displayUniqueId = displayUniqueId; + if (Build.VERSION.SDK_INT >= AndroidVersions.API_23_ANDROID_6_0) { + HandlerThread thread = new HandlerThread("UHidManager"); + thread.start(); + queue = thread.getLooper().getQueue(); + } else { + queue = null; + } + } + + public void open(int id, int vendorId, int productId, String name, byte[] reportDesc) throws IOException { + try { + FileDescriptor fd = Os.open("/dev/uhid", OsConstants.O_RDWR, 0); + try { + // First UHID device added + boolean firstDevice = fds.isEmpty(); + + FileDescriptor old = fds.put(id, fd); + if (old != null) { + Ln.w("Duplicate UHID id: " + id); + close(old); + } + + String phys = mustUseInputPort() ? INPUT_PORT : null; + byte[] req = buildUhidCreate2Req(vendorId, productId, name, reportDesc, phys); + Os.write(fd, req, 0, req.length); + + if (firstDevice) { + addUniqueIdAssociation(); + } + registerUhidListener(id, fd); + } catch (Exception e) { + close(fd); + throw e; + } + } catch (ErrnoException e) { + throw new IOException(e); + } + } + + private void registerUhidListener(int id, FileDescriptor fd) { + if (Build.VERSION.SDK_INT >= AndroidVersions.API_23_ANDROID_6_0) { + queue.addOnFileDescriptorEventListener(fd, MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT, (fd2, events) -> { + try { + buffer.clear(); + int r = Os.read(fd2, buffer); + buffer.flip(); + if (r > 0) { + int type = buffer.getInt(); + if (type == UHID_OUTPUT) { + byte[] data = extractHidOutputData(buffer); + if (data != null) { + DeviceMessage msg = DeviceMessage.createUhidOutput(id, data); + sender.send(msg); + } + } + } + } catch (ErrnoException | InterruptedIOException e) { + Ln.e("Failed to read UHID output", e); + return 0; + } + return events; + }); + } + } + + private void unregisterUhidListener(FileDescriptor fd) { + if (Build.VERSION.SDK_INT >= AndroidVersions.API_23_ANDROID_6_0) { + queue.removeOnFileDescriptorEventListener(fd); + } + } + + private static byte[] extractHidOutputData(ByteBuffer buffer) { + /* + * #define UHID_DATA_MAX 4096 + * struct uhid_event { + * uint32_t type; + * union { + * // ... + * struct uhid_output_req { + * __u8 data[UHID_DATA_MAX]; + * __u16 size; + * __u8 rtype; + * }; + * }; + * } __attribute__((__packed__)); + */ + + if (buffer.remaining() < 4099) { + Ln.w("Incomplete HID output"); + return null; + } + int size = buffer.getShort(buffer.position() + 4096) & 0xFFFF; + if (size > 4096) { + Ln.w("Incorrect HID output size: " + size); + return null; + } + byte[] data = new byte[size]; + buffer.get(data); + return data; + } + + public void writeInput(int id, byte[] data) throws IOException { + FileDescriptor fd = fds.get(id); + if (fd == null) { + Ln.w("Unknown UHID id: " + id); + return; + } + + try { + byte[] req = buildUhidInput2Req(data); + Os.write(fd, req, 0, req.length); + } catch (ErrnoException e) { + throw new IOException(e); + } + } + + private static byte[] buildUhidCreate2Req(int vendorId, int productId, String name, byte[] reportDesc, String phys) { + /* + * struct uhid_event { + * uint32_t type; + * union { + * // ... + * struct uhid_create2_req { + * uint8_t name[128]; + * uint8_t phys[64]; + * uint8_t uniq[64]; + * uint16_t rd_size; + * uint16_t bus; + * uint32_t vendor; + * uint32_t product; + * uint32_t version; + * uint32_t country; + * uint8_t rd_data[HID_MAX_DESCRIPTOR_SIZE]; + * }; + * }; + * } __attribute__((__packed__)); + */ + + ByteBuffer buf = ByteBuffer.allocate(280 + reportDesc.length).order(ByteOrder.nativeOrder()); + buf.putInt(UHID_CREATE2); + + String actualName = name.isEmpty() ? "scrcpy" : name; + byte[] nameBytes = actualName.getBytes(StandardCharsets.UTF_8); + int nameLen = StringUtils.getUtf8TruncationIndex(nameBytes, 127); + assert nameLen <= 127; + buf.put(nameBytes, 0, nameLen); + + if (phys != null) { + buf.position(4 + 128); + byte[] physBytes = phys.getBytes(StandardCharsets.US_ASCII); + assert physBytes.length <= 63; + buf.put(physBytes); + } + + buf.position(4 + 256); + buf.putShort((short) reportDesc.length); + buf.putShort(BUS_VIRTUAL); + buf.putInt(vendorId); + buf.putInt(productId); + buf.putInt(0); // version + buf.putInt(0); // country; + buf.put(reportDesc); + return buf.array(); + } + + private static byte[] buildUhidInput2Req(byte[] data) { + /* + * struct uhid_event { + * uint32_t type; + * union { + * // ... + * struct uhid_input2_req { + * uint16_t size; + * uint8_t data[UHID_DATA_MAX]; + * }; + * }; + * } __attribute__((__packed__)); + */ + + ByteBuffer buf = ByteBuffer.allocate(6 + data.length).order(ByteOrder.nativeOrder()); + buf.putInt(UHID_INPUT2); + buf.putShort((short) data.length); + buf.put(data); + return buf.array(); + } + + public void close(int id) { + // Linux: Documentation/hid/uhid.rst + // If you close() the fd, the device is automatically unregistered and destroyed internally. + FileDescriptor fd = fds.remove(id); + if (fd != null) { + unregisterUhidListener(fd); + close(fd); + + if (fds.isEmpty()) { + // Last UHID device removed + removeUniqueIdAssociation(); + } + } else { + Ln.w("Closing unknown UHID device: " + id); + } + } + + public void closeAll() { + if (fds.isEmpty()) { + return; + } + + for (FileDescriptor fd : fds.values()) { + close(fd); + } + + removeUniqueIdAssociation(); + } + + private static void close(FileDescriptor fd) { + try { + Os.close(fd); + } catch (ErrnoException e) { + Ln.e("Failed to close uhid: " + e.getMessage()); + } + } + + private boolean mustUseInputPort() { + return Build.VERSION.SDK_INT >= AndroidVersions.API_35_ANDROID_15 && displayUniqueId != null; + } + + private void addUniqueIdAssociation() { + if (mustUseInputPort()) { + ServiceManager.getInputManager().addUniqueIdAssociationByPort(INPUT_PORT, displayUniqueId); + } + } + + private void removeUniqueIdAssociation() { + if (mustUseInputPort()) { + ServiceManager.getInputManager().removeUniqueIdAssociationByPort(INPUT_PORT); + } + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/device/DesktopConnection.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/device/DesktopConnection.java new file mode 100644 index 0000000..db75aec --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/device/DesktopConnection.java @@ -0,0 +1,178 @@ +package com.genymobile.scrcpy.device; + +import com.genymobile.scrcpy.control.ControlChannel; +import com.genymobile.scrcpy.util.IO; +import com.genymobile.scrcpy.util.StringUtils; + +import android.net.LocalServerSocket; +import android.net.LocalSocket; +import android.net.LocalSocketAddress; + +import java.io.Closeable; +import java.io.FileDescriptor; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +public final class DesktopConnection implements Closeable { + + private static final int DEVICE_NAME_FIELD_LENGTH = 64; + + private static final String SOCKET_NAME_PREFIX = "scrcpy"; + + private final LocalSocket videoSocket; + private final FileDescriptor videoFd; + + private final LocalSocket audioSocket; + private final FileDescriptor audioFd; + + private final LocalSocket controlSocket; + private final ControlChannel controlChannel; + + private DesktopConnection(LocalSocket videoSocket, LocalSocket audioSocket, LocalSocket controlSocket) throws IOException { + this.videoSocket = videoSocket; + this.audioSocket = audioSocket; + this.controlSocket = controlSocket; + + videoFd = videoSocket != null ? videoSocket.getFileDescriptor() : null; + audioFd = audioSocket != null ? audioSocket.getFileDescriptor() : null; + controlChannel = controlSocket != null ? new ControlChannel(controlSocket) : null; + } + + private static LocalSocket connect(String abstractName) throws IOException { + LocalSocket localSocket = new LocalSocket(); + localSocket.connect(new LocalSocketAddress(abstractName)); + return localSocket; + } + + private static String getSocketName(int scid) { + if (scid == -1) { + // If no SCID is set, use "scrcpy" to simplify using scrcpy-server alone + return SOCKET_NAME_PREFIX; + } + + return SOCKET_NAME_PREFIX + String.format("_%08x", scid); + } + + public static DesktopConnection open(int scid, boolean tunnelForward, boolean video, boolean audio, boolean control, boolean sendDummyByte) + throws IOException { + String socketName = getSocketName(scid); + + LocalSocket videoSocket = null; + LocalSocket audioSocket = null; + LocalSocket controlSocket = null; + try { + if (tunnelForward) { + try (LocalServerSocket localServerSocket = new LocalServerSocket(socketName)) { + if (video) { + videoSocket = localServerSocket.accept(); + if (sendDummyByte) { + // send one byte so the client may read() to detect a connection error + videoSocket.getOutputStream().write(0); + sendDummyByte = false; + } + } + if (audio) { + audioSocket = localServerSocket.accept(); + if (sendDummyByte) { + // send one byte so the client may read() to detect a connection error + audioSocket.getOutputStream().write(0); + sendDummyByte = false; + } + } + if (control) { + controlSocket = localServerSocket.accept(); + if (sendDummyByte) { + // send one byte so the client may read() to detect a connection error + controlSocket.getOutputStream().write(0); + sendDummyByte = false; + } + } + } + } else { + if (video) { + videoSocket = connect(socketName); + } + if (audio) { + audioSocket = connect(socketName); + } + if (control) { + controlSocket = connect(socketName); + } + } + } catch (IOException | RuntimeException e) { + if (videoSocket != null) { + videoSocket.close(); + } + if (audioSocket != null) { + audioSocket.close(); + } + if (controlSocket != null) { + controlSocket.close(); + } + throw e; + } + + return new DesktopConnection(videoSocket, audioSocket, controlSocket); + } + + private LocalSocket getFirstSocket() { + if (videoSocket != null) { + return videoSocket; + } + if (audioSocket != null) { + return audioSocket; + } + return controlSocket; + } + + public void shutdown() throws IOException { + if (videoSocket != null) { + videoSocket.shutdownInput(); + videoSocket.shutdownOutput(); + } + if (audioSocket != null) { + audioSocket.shutdownInput(); + audioSocket.shutdownOutput(); + } + if (controlSocket != null) { + controlSocket.shutdownInput(); + controlSocket.shutdownOutput(); + } + } + + public void close() throws IOException { + if (videoSocket != null) { + videoSocket.close(); + } + if (audioSocket != null) { + audioSocket.close(); + } + if (controlSocket != null) { + controlSocket.close(); + } + } + + public void sendDeviceMeta(String deviceName) throws IOException { + byte[] buffer = new byte[DEVICE_NAME_FIELD_LENGTH]; + + byte[] deviceNameBytes = deviceName.getBytes(StandardCharsets.UTF_8); + int len = StringUtils.getUtf8TruncationIndex(deviceNameBytes, DEVICE_NAME_FIELD_LENGTH - 1); + System.arraycopy(deviceNameBytes, 0, buffer, 0, len); + // byte[] are always 0-initialized in java, no need to set '\0' explicitly + + FileDescriptor fd = getFirstSocket().getFileDescriptor(); + IO.writeFully(fd, buffer, 0, buffer.length); + } + + public FileDescriptor getVideoFd() { + return videoFd; + } + + public FileDescriptor getAudioFd() { + return audioFd; + } + + public ControlChannel getControlChannel() { + return controlChannel; + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/device/Device.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/device/Device.java new file mode 100644 index 0000000..615675a --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/device/Device.java @@ -0,0 +1,326 @@ +package com.genymobile.scrcpy.device; + +import com.genymobile.scrcpy.AndroidVersions; +import com.genymobile.scrcpy.FakeContext; +import com.genymobile.scrcpy.display.DisplayInfo; +import com.genymobile.scrcpy.model.DeviceApp; +import com.genymobile.scrcpy.util.Ln; +import com.genymobile.scrcpy.wrappers.ActivityManager; +import com.genymobile.scrcpy.wrappers.ClipboardManager; +import com.genymobile.scrcpy.wrappers.DisplayControl; +import com.genymobile.scrcpy.wrappers.InputManager; +import com.genymobile.scrcpy.wrappers.ServiceManager; +import com.genymobile.scrcpy.wrappers.SurfaceControl; +import com.genymobile.scrcpy.wrappers.WindowManager; + +import android.annotation.SuppressLint; +import android.app.ActivityOptions; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.Bundle; +import android.os.IBinder; +import android.os.SystemClock; +import android.view.InputDevice; +import android.view.InputEvent; +import android.view.KeyCharacterMap; +import android.view.KeyEvent; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +public final class Device { + + public static final int DISPLAY_ID_NONE = -1; + + public static final int POWER_MODE_OFF = SurfaceControl.POWER_MODE_OFF; + public static final int POWER_MODE_NORMAL = SurfaceControl.POWER_MODE_NORMAL; + + public static final int INJECT_MODE_ASYNC = InputManager.INJECT_INPUT_EVENT_MODE_ASYNC; + public static final int INJECT_MODE_WAIT_FOR_RESULT = InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT; + public static final int INJECT_MODE_WAIT_FOR_FINISH = InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH; + + // The new display power method introduced in Android 15 does not work as expected: + // + private static final boolean USE_ANDROID_15_DISPLAY_POWER = false; + + private Device() { + // not instantiable + } + + public static String getDeviceName() { + return Build.MODEL; + } + + public static boolean supportsInputEvents(int displayId) { + // main display or any display on Android >= 10 + return displayId == 0 || Build.VERSION.SDK_INT >= AndroidVersions.API_29_ANDROID_10; + } + + public static boolean injectEvent(InputEvent inputEvent, int displayId, int injectMode) { + if (!supportsInputEvents(displayId)) { + throw new AssertionError("Could not inject input event if !supportsInputEvents()"); + } + + if (displayId != 0 && !InputManager.setDisplayId(inputEvent, displayId)) { + return false; + } + + return ServiceManager.getInputManager().injectInputEvent(inputEvent, injectMode); + } + + public static boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState, int displayId, int injectMode) { + long now = SystemClock.uptimeMillis(); + KeyEvent event = new KeyEvent(now, now, action, keyCode, repeat, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, + InputDevice.SOURCE_KEYBOARD); + return injectEvent(event, displayId, injectMode); + } + + public static boolean pressReleaseKeycode(int keyCode, int displayId, int injectMode) { + return injectKeyEvent(KeyEvent.ACTION_DOWN, keyCode, 0, 0, displayId, injectMode) + && injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0, displayId, injectMode); + } + + public static boolean isScreenOn(int displayId) { + assert displayId != DISPLAY_ID_NONE; + return ServiceManager.getPowerManager().isScreenOn(displayId); + } + + public static void keepActive(int displayId) { + assert displayId != DISPLAY_ID_NONE; + ServiceManager.getPowerManager().userActivity(displayId); + } + + public static void expandNotificationPanel() { + ServiceManager.getStatusBarManager().expandNotificationsPanel(); + } + + public static void expandSettingsPanel() { + ServiceManager.getStatusBarManager().expandSettingsPanel(); + } + + public static void collapsePanels() { + ServiceManager.getStatusBarManager().collapsePanels(); + } + + public static String getClipboardText() { + ClipboardManager clipboardManager = ServiceManager.getClipboardManager(); + if (clipboardManager == null) { + return null; + } + CharSequence s = clipboardManager.getText(); + if (s == null) { + return null; + } + return s.toString(); + } + + public static boolean setClipboardText(String text) { + ClipboardManager clipboardManager = ServiceManager.getClipboardManager(); + if (clipboardManager == null) { + return false; + } + + String currentClipboard = getClipboardText(); + if (currentClipboard != null && currentClipboard.equals(text)) { + // The clipboard already contains the requested text. + // Since pasting text from the computer involves setting the device clipboard, it could be set twice on a copy-paste. This would cause + // the clipboard listeners to be notified twice, and that would flood the Android keyboard clipboard history. To workaround this + // problem, do not explicitly set the clipboard text if it already contains the expected content. + return false; + } + + return clipboardManager.setText(text); + } + + public static boolean setDisplayPower(int displayId, boolean on) { + assert displayId != Device.DISPLAY_ID_NONE; + + if (USE_ANDROID_15_DISPLAY_POWER && Build.VERSION.SDK_INT >= AndroidVersions.API_35_ANDROID_15) { + return ServiceManager.getDisplayManager().requestDisplayPower(displayId, on); + } + + boolean applyToMultiPhysicalDisplays = Build.VERSION.SDK_INT >= AndroidVersions.API_29_ANDROID_10; + + if (applyToMultiPhysicalDisplays + && Build.VERSION.SDK_INT >= AndroidVersions.API_34_ANDROID_14 + && Build.BRAND.equalsIgnoreCase("honor") + && SurfaceControl.hasGetBuildInDisplayMethod()) { + // Workaround for Honor devices with Android 14: + // - + // - + applyToMultiPhysicalDisplays = false; + } + + int mode = on ? POWER_MODE_NORMAL : POWER_MODE_OFF; + if (applyToMultiPhysicalDisplays) { + // On Android 14, these internal methods have been moved to DisplayControl + boolean useDisplayControl = + Build.VERSION.SDK_INT >= AndroidVersions.API_34_ANDROID_14 && !SurfaceControl.hasGetPhysicalDisplayIdsMethod(); + + // Change the power mode for all physical displays + long[] physicalDisplayIds = useDisplayControl ? DisplayControl.getPhysicalDisplayIds() : SurfaceControl.getPhysicalDisplayIds(); + if (physicalDisplayIds == null) { + Ln.e("Could not get physical display ids"); + return false; + } + + boolean allOk = true; + for (long physicalDisplayId : physicalDisplayIds) { + IBinder binder = useDisplayControl ? DisplayControl.getPhysicalDisplayToken( + physicalDisplayId) : SurfaceControl.getPhysicalDisplayToken(physicalDisplayId); + allOk &= SurfaceControl.setDisplayPowerMode(binder, mode); + } + return allOk; + } + + // Older Android versions, only 1 display + IBinder d = SurfaceControl.getBuiltInDisplay(); + if (d == null) { + Ln.e("Could not get built-in display"); + return false; + } + return SurfaceControl.setDisplayPowerMode(d, mode); + } + + public static boolean powerOffScreen(int displayId) { + assert displayId != DISPLAY_ID_NONE; + + if (!isScreenOn(displayId)) { + return true; + } + return pressReleaseKeycode(KeyEvent.KEYCODE_POWER, displayId, Device.INJECT_MODE_ASYNC); + } + + /** + * Disable auto-rotation (if enabled), set the screen rotation and re-enable auto-rotation (if it was enabled). + */ + public static void rotateDevice(int displayId) { + assert displayId != DISPLAY_ID_NONE; + + WindowManager wm = ServiceManager.getWindowManager(); + + boolean accelerometerRotation = !wm.isRotationFrozen(displayId); + + int currentRotation = getCurrentRotation(displayId); + int newRotation = (currentRotation & 1) ^ 1; // 0->1, 1->0, 2->1, 3->0 + String newRotationString = newRotation == 0 ? "portrait" : "landscape"; + + Ln.i("Device rotation requested: " + newRotationString); + wm.freezeRotation(displayId, newRotation); + + // restore auto-rotate if necessary + if (accelerometerRotation) { + wm.thawRotation(displayId); + } + } + + private static int getCurrentRotation(int displayId) { + assert displayId != DISPLAY_ID_NONE; + + if (displayId == 0) { + return ServiceManager.getWindowManager().getRotation(); + } + + DisplayInfo displayInfo = ServiceManager.getDisplayManager().getDisplayInfo(displayId); + return displayInfo.getRotation(); + } + + public static List listApps() { + List apps = new ArrayList<>(); + PackageManager pm = FakeContext.get().getPackageManager(); + for (ApplicationInfo appInfo : getLaunchableApps(pm)) { + apps.add(toApp(pm, appInfo)); + } + + return apps; + } + + @SuppressLint("QueryPermissionsNeeded") + private static List getLaunchableApps(PackageManager pm) { + List result = new ArrayList<>(); + for (ApplicationInfo appInfo : pm.getInstalledApplications(PackageManager.GET_META_DATA)) { + if (appInfo.enabled && getLaunchIntent(pm, appInfo.packageName) != null) { + result.add(appInfo); + } + } + + return result; + } + + public static Intent getLaunchIntent(PackageManager pm, String packageName) { + Intent launchIntent = pm.getLaunchIntentForPackage(packageName); + if (launchIntent != null) { + return launchIntent; + } + + return pm.getLeanbackLaunchIntentForPackage(packageName); + } + + private static DeviceApp toApp(PackageManager pm, ApplicationInfo appInfo) { + String name = pm.getApplicationLabel(appInfo).toString(); + boolean system = (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0; + return new DeviceApp(appInfo.packageName, name, system); + } + + @SuppressLint("QueryPermissionsNeeded") + public static DeviceApp findByPackageName(String packageName) { + PackageManager pm = FakeContext.get().getPackageManager(); + // No need to filter by "launchable" apps, an error will be reported on start if the app is not launchable + for (ApplicationInfo appInfo : pm.getInstalledApplications(PackageManager.GET_META_DATA)) { + if (packageName.equals(appInfo.packageName)) { + return toApp(pm, appInfo); + } + } + + 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)) { + 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) { + PackageManager pm = FakeContext.get().getPackageManager(); + + Intent launchIntent = getLaunchIntent(pm, packageName); + if (launchIntent == null) { + Ln.w("Cannot create launch intent for app " + packageName); + return; + } + + launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + Bundle options = null; + if (Build.VERSION.SDK_INT >= AndroidVersions.API_26_ANDROID_8_0) { + ActivityOptions launchOptions = ActivityOptions.makeBasic(); + launchOptions.setLaunchDisplayId(displayId); + options = launchOptions.toBundle(); + } + + ActivityManager am = ServiceManager.getActivityManager(); + if (forceStop) { + am.forceStopPackage(packageName); + } + am.startActivity(launchIntent, options); + } + + public static void sendBroadcast(Intent intent) { + ServiceManager.getActivityManager().sendBroadcast(intent); + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/device/Streamer.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/device/Streamer.java new file mode 100644 index 0000000..e793f67 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/device/Streamer.java @@ -0,0 +1,205 @@ +package com.genymobile.scrcpy.device; + +import com.genymobile.scrcpy.audio.AudioCodec; +import com.genymobile.scrcpy.model.Codec; +import com.genymobile.scrcpy.util.IO; + +import android.media.MediaCodec; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; + +public final class Streamer { + + private static final long PACKET_FLAG_SESSION = 1L << 63; + private static final long PACKET_FLAG_CONFIG = 1L << 62; + private static final long PACKET_FLAG_KEY_FRAME = 1L << 61; + + private final FileDescriptor fd; + private final Codec codec; + private final boolean sendStreamMeta; + private final boolean sendFrameMeta; + + private final ByteBuffer headerBuffer = ByteBuffer.allocate(12); + + public Streamer(FileDescriptor fd, Codec codec, boolean sendCodecMeta, boolean sendFrameMeta) { + this.fd = fd; + this.codec = codec; + this.sendStreamMeta = sendCodecMeta; + this.sendFrameMeta = sendFrameMeta; + } + + public Codec getCodec() { + return codec; + } + + public void writeAudioHeader() throws IOException { + if (sendStreamMeta) { + ByteBuffer buffer = ByteBuffer.allocate(4); + buffer.putInt(codec.getId()); + buffer.flip(); + IO.writeFully(fd, buffer); + } + } + + public void writeVideoHeader() throws IOException { + if (sendStreamMeta) { + ByteBuffer buffer = ByteBuffer.allocate(4); + buffer.putInt(codec.getId()); + buffer.flip(); + IO.writeFully(fd, buffer); + } + } + + public void writeDisableStream(boolean error) throws IOException { + // Writing a specific code as codec-id means that the device disables the stream + // code 0: it explicitly disables the stream (because it could not capture audio), scrcpy should continue mirroring video only + // code 1: a configuration error occurred, scrcpy must be stopped + byte[] code = new byte[4]; + if (error) { + code[3] = 1; + } + IO.writeFully(fd, code, 0, code.length); + } + + public void writePacket(ByteBuffer buffer, long pts, boolean config, boolean keyFrame) throws IOException { + if (config) { + if (codec == AudioCodec.OPUS) { + fixOpusConfigPacket(buffer); + } else if (codec == AudioCodec.FLAC) { + fixFlacConfigPacket(buffer); + } + } + + if (sendFrameMeta) { + writeFrameMeta(fd, buffer.remaining(), pts, config, keyFrame); + } + + IO.writeFully(fd, buffer); + } + + public void writePacket(ByteBuffer codecBuffer, MediaCodec.BufferInfo bufferInfo) throws IOException { + long pts = bufferInfo.presentationTimeUs; + boolean config = (bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0; + boolean keyFrame = (bufferInfo.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0; + writePacket(codecBuffer, pts, config, keyFrame); + } + + public void writeSessionMeta(int width, int height, boolean isClientResize) throws IOException { + if (sendStreamMeta) { + headerBuffer.clear(); + + int flags = (int) (PACKET_FLAG_SESSION >> 32); // set the first bit to 1 + if (isClientResize) { + flags |= 1; + } + headerBuffer.putInt(flags); + headerBuffer.putInt(width); + headerBuffer.putInt(height); + headerBuffer.flip(); + IO.writeFully(fd, headerBuffer); + } + } + + private void writeFrameMeta(FileDescriptor fd, int packetSize, long pts, boolean config, boolean keyFrame) throws IOException { + headerBuffer.clear(); + + long ptsAndFlags; + if (config) { + ptsAndFlags = PACKET_FLAG_CONFIG; // non-media data packet + } else { + ptsAndFlags = pts; + if (keyFrame) { + ptsAndFlags |= PACKET_FLAG_KEY_FRAME; + } + } + + headerBuffer.putLong(ptsAndFlags); + headerBuffer.putInt(packetSize); + headerBuffer.flip(); + IO.writeFully(fd, headerBuffer); + } + + private static void fixOpusConfigPacket(ByteBuffer buffer) throws IOException { + // Here is an example of the config packet received for an OPUS stream: + // + // 00000000 41 4f 50 55 53 48 44 52 13 00 00 00 00 00 00 00 |AOPUSHDR........| + // -------------- BELOW IS THE PART WE MUST PUT AS EXTRADATA ------------------- + // 00000010 4f 70 75 73 48 65 61 64 01 01 38 01 80 bb 00 00 |OpusHead..8.....| + // 00000020 00 00 00 |... | + // ------------------------------------------------------------------------------ + // 00000020 41 4f 50 55 53 44 4c 59 08 00 00 00 00 | AOPUSDLY.....| + // 00000030 00 00 00 a0 2e 63 00 00 00 00 00 41 4f 50 55 53 |.....c.....AOPUS| + // 00000040 50 52 4c 08 00 00 00 00 00 00 00 00 b4 c4 04 00 |PRL.............| + // 00000050 00 00 00 |...| + // + // Each "section" is prefixed by a 64-bit ID and a 64-bit length. + // + // + + if (buffer.remaining() < 16) { + throw new IOException("Not enough data in OPUS config packet"); + } + + final byte[] opusHeaderId = {'A', 'O', 'P', 'U', 'S', 'H', 'D', 'R'}; + byte[] idBuffer = new byte[8]; + buffer.get(idBuffer); + if (!Arrays.equals(idBuffer, opusHeaderId)) { + throw new IOException("OPUS header not found"); + } + + // The size is in native byte-order + long sizeLong = buffer.getLong(); + if (sizeLong < 0 || sizeLong >= 0x7FFFFFFF) { + throw new IOException("Invalid block size in OPUS header: " + sizeLong); + } + + int size = (int) sizeLong; + if (buffer.remaining() < size) { + throw new IOException("Not enough data in OPUS header (invalid size: " + size + ")"); + } + + // Set the buffer to point to the OPUS header slice + buffer.limit(buffer.position() + size); + } + + private static void fixFlacConfigPacket(ByteBuffer buffer) throws IOException { + // 00000000 66 4c 61 43 00 00 00 22 |fLaC..." | + // -------------- BELOW IS THE PART WE MUST PUT AS EXTRADATA ------------------- + // 00000000 10 00 10 00 00 00 00 00 | ........| + // 00000010 00 00 0b b8 02 f0 00 00 00 00 00 00 00 00 00 00 |................| + // 00000020 00 00 00 00 00 00 00 00 00 00 |.......... | + // ------------------------------------------------------------------------------ + // 00000020 84 00 00 28 20 00 | ...( .| + // 00000030 00 00 72 65 66 65 72 65 6e 63 65 20 6c 69 62 46 |..reference libF| + // 00000040 4c 41 43 20 31 2e 33 2e 32 20 32 30 32 32 31 30 |LAC 1.3.2 202210| + // 00000050 32 32 00 00 00 00 |22....| + // + // + + if (buffer.remaining() < 8) { + throw new IOException("Not enough data in FLAC config packet"); + } + + final byte[] flacHeaderId = {'f', 'L', 'a', 'C'}; + byte[] idBuffer = new byte[4]; + buffer.get(idBuffer); + if (!Arrays.equals(idBuffer, flacHeaderId)) { + throw new IOException("FLAC header not found"); + } + + // The size is in big-endian + buffer.order(ByteOrder.BIG_ENDIAN); + + int size = buffer.getInt(); + if (buffer.remaining() < size) { + throw new IOException("Not enough data in FLAC header (invalid size: " + size + ")"); + } + + // Set the buffer to point to the FLAC header slice + buffer.limit(buffer.position() + size); + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/display/DisplayInfo.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/display/DisplayInfo.java new file mode 100644 index 0000000..ea966a3 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/display/DisplayInfo.java @@ -0,0 +1,53 @@ +package com.genymobile.scrcpy.display; + +import com.genymobile.scrcpy.model.Size; + +public final class DisplayInfo { + private final int displayId; + private final Size size; + private final int rotation; + private final int layerStack; + private final int flags; + private final int dpi; + private final String uniqueId; + + public static final int FLAG_SUPPORTS_PROTECTED_BUFFERS = 0x00000001; + + public DisplayInfo(int displayId, Size size, int rotation, int layerStack, int flags, int dpi, String uniqueId) { + this.displayId = displayId; + this.size = size; + this.rotation = rotation; + this.layerStack = layerStack; + this.flags = flags; + this.dpi = dpi; + this.uniqueId = uniqueId; + } + + public int getDisplayId() { + return displayId; + } + + public Size getSize() { + return size; + } + + public int getRotation() { + return rotation; + } + + public int getLayerStack() { + return layerStack; + } + + public int getFlags() { + return flags; + } + + public int getDpi() { + return dpi; + } + + public String getUniqueId() { + return uniqueId; + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/display/DisplayMonitor.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/display/DisplayMonitor.java new file mode 100644 index 0000000..f6d871c --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/display/DisplayMonitor.java @@ -0,0 +1,144 @@ +package com.genymobile.scrcpy.display; + +import com.genymobile.scrcpy.AndroidVersions; +import com.genymobile.scrcpy.device.Device; +import com.genymobile.scrcpy.util.Ln; +import com.genymobile.scrcpy.wrappers.DisplayManager; +import com.genymobile.scrcpy.wrappers.DisplayWindowListener; +import com.genymobile.scrcpy.wrappers.ServiceManager; + +import android.content.res.Configuration; +import android.os.Build; +import android.os.Handler; +import android.os.HandlerThread; +import android.view.IDisplayWindowListener; + +public class DisplayMonitor { + + public interface Listener { + void onDisplayPropertiesChanged(DisplayProperties props); + } + + // On Android 14, DisplayListener may be broken (it never sends events). This is fixed in recent Android 14 upgrades, but we can't really + // detect it directly, so register a DisplayWindowListener (introduced in Android 11) to listen to configuration changes instead. + // It has been broken again after an Android 15 upgrade: + // So use the default method only before Android 14. + private static final boolean USE_DEFAULT_METHOD = Build.VERSION.SDK_INT < AndroidVersions.API_34_ANDROID_14; + + private DisplayManager.DisplayListenerHandle displayListenerHandle; + private HandlerThread handlerThread; + + private IDisplayWindowListener displayWindowListener; + + private int displayId = Device.DISPLAY_ID_NONE; + + private DisplayProperties props; + + private Listener listener; + + public void start(int displayId, Listener listener) { + // Once started, the listener and the displayId must never change + assert listener != null; + this.listener = listener; + + assert this.displayId == Device.DISPLAY_ID_NONE; + this.displayId = displayId; + + if (USE_DEFAULT_METHOD) { + handlerThread = new HandlerThread("DisplayListener"); + handlerThread.start(); + Handler handler = new Handler(handlerThread.getLooper()); + displayListenerHandle = ServiceManager.getDisplayManager().registerDisplayListener(eventDisplayId -> { + if (Ln.isEnabled(Ln.Level.VERBOSE)) { + Ln.v("DisplayMonitor: onDisplayChanged(" + eventDisplayId + ")"); + } + + if (eventDisplayId == displayId) { + try { + checkDisplayPropertiesChanged(); + } catch (Throwable e) { + Ln.e("DisplayMonitor error", e); + throw e; + } + } + }, handler); + } else { + displayWindowListener = new DisplayWindowListener() { + @Override + public void onDisplayConfigurationChanged(int eventDisplayId, Configuration newConfig) { + if (Ln.isEnabled(Ln.Level.VERBOSE)) { + Ln.v("DisplayMonitor: onDisplayConfigurationChanged(" + eventDisplayId + ")"); + } + + if (eventDisplayId == displayId) { + try { + checkDisplayPropertiesChanged(); + } catch (Throwable e) { + Ln.e("DisplayMonitor error", e); + throw e; + } + } + } + }; + ServiceManager.getWindowManager().registerDisplayWindowListener(displayWindowListener); + } + } + + /** + * Stop and release the monitor. + *

+ * It must not be used anymore. + * It is ok to call this method even if {@link #start(int, Listener)} was not called. + */ + public void stopAndRelease() { + if (USE_DEFAULT_METHOD) { + // displayListenerHandle may be null if registration failed + if (displayListenerHandle != null) { + ServiceManager.getDisplayManager().unregisterDisplayListener(displayListenerHandle); + displayListenerHandle = null; + } + + if (handlerThread != null) { + handlerThread.quitSafely(); + } + } else if (displayWindowListener != null) { + ServiceManager.getWindowManager().unregisterDisplayWindowListener(displayWindowListener); + } + } + + private synchronized DisplayProperties getAndSetDisplayProperties(DisplayProperties props) { + DisplayProperties oldProps = this.props; + this.props = props; + return oldProps; + } + + public synchronized void setSessionDisplayProperties(DisplayProperties props) { + this.props = props; + } + + private void checkDisplayPropertiesChanged() { + DisplayInfo di = ServiceManager.getDisplayManager().getDisplayInfo(displayId); + if (di == null) { + Ln.w("DisplayInfo for " + displayId + " cannot be retrieved"); + // We can't compare with the current properties, so reset unconditionally + DisplayProperties oldProps = getAndSetDisplayProperties(null); // exchange with synchronization + if (Ln.isEnabled(Ln.Level.VERBOSE)) { + Ln.v("DisplayMonitor: " + oldProps + " -> (unknown)"); + } + listener.onDisplayPropertiesChanged(null); + } else { + DisplayProperties newProps = new DisplayProperties(di.getSize(), di.getRotation()); + + DisplayProperties oldProps = getAndSetDisplayProperties(newProps); // exchange with synchronization + if (!newProps.equals(oldProps)) { + // Reset only if the properties are different + if (Ln.isEnabled(Ln.Level.VERBOSE)) { + Ln.v("DisplayMonitor: " + oldProps + " -> " + newProps); + } + listener.onDisplayPropertiesChanged(newProps); + } else if (Ln.isEnabled(Ln.Level.VERBOSE)) { + Ln.v("DisplayMonitor: " + newProps + " (unchanged)"); + } + } + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/display/DisplayProperties.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/display/DisplayProperties.java new file mode 100644 index 0000000..d365833 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/display/DisplayProperties.java @@ -0,0 +1,52 @@ +package com.genymobile.scrcpy.display; + +import com.genymobile.scrcpy.model.Size; + +import java.util.Objects; + +public final class DisplayProperties { + private Size size; + private int rotation; + + public DisplayProperties(Size size, int rotation) { + assert size != null; + assert rotation >= 0 && rotation < 4; + this.size = size; + this.rotation = rotation; + } + + public Size getSize() { + return size; + } + + public void setSize(Size size) { + this.size = size; + } + + public int getRotation() { + return rotation; + } + + public void setRotation(int rotation) { + this.rotation = rotation; + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) { + return false; + } + DisplayProperties that = (DisplayProperties) o; + return rotation == that.rotation && Objects.equals(size, that.size); + } + + @Override + public int hashCode() { + return Objects.hash(size, rotation); + } + + @Override + public String toString() { + return size + " [rotation=" + rotation + "]"; + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/display/DisplayPropertiesTracker.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/display/DisplayPropertiesTracker.java new file mode 100644 index 0000000..adc606a --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/display/DisplayPropertiesTracker.java @@ -0,0 +1,84 @@ +package com.genymobile.scrcpy.display; + +import android.os.SystemClock; + +import java.util.ArrayList; +import java.util.List; + +public class DisplayPropertiesTracker { + + private static final long PENDING_CACHE_DURATION = 3000; // ms + + private static class PendingChange { + private final DisplayProperties props; + private final long timestamp; + + PendingChange(DisplayProperties props, long timestamp) { + this.props = props; + this.timestamp = timestamp; + } + } + + private final List pending = new ArrayList<>(); + + public synchronized void pushClientRequest(DisplayProperties props) { + long now = SystemClock.uptimeMillis(); + pending.add(new PendingChange(props, now)); + } + + /** + * Function to be called when the display properties changed. + * + * @param props the new display properties + * @return {@code true} if this change is the result of a client request + */ + public synchronized boolean onChanged(DisplayProperties props) { + cleanExpired(); + int index = getMatchingPendingIndex(props); + if (index == -1) { + return false; + } + + pending.subList(0, index + 1).clear(); + return true; + } + + private int getMatchingPendingIndex(DisplayProperties props) { + for (int i = 0; i < pending.size(); ++i) { + if (pending.get(i).props.equals(props)) { + return i; + } + } + return -1; + } + + private int getFirstNonExpiredIndex() { + long now = SystemClock.uptimeMillis(); + for (int i = 0; i < pending.size(); ++i) { + if (pending.get(i).timestamp + PENDING_CACHE_DURATION >= now) { + return i; + } + } + return -1; + } + + private void cleanExpired() { + if (pending.isEmpty()) { + return; + } + + int firstNonExpiredIndex = getFirstNonExpiredIndex(); + if (firstNonExpiredIndex == 0) { + // All items are fresh + return; + } + + if (firstNonExpiredIndex == -1) { + // All items have expired + pending.clear(); + } else { + // Remove all the items up to the first non-expired index + pending.subList(0, firstNonExpiredIndex).clear(); + } + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/display/DisplayResizeDebouncer.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/display/DisplayResizeDebouncer.java new file mode 100644 index 0000000..0b961c6 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/display/DisplayResizeDebouncer.java @@ -0,0 +1,85 @@ +package com.genymobile.scrcpy.display; + +import com.genymobile.scrcpy.model.Size; +import com.genymobile.scrcpy.util.Ln; + +import android.os.SystemClock; + +public final class DisplayResizeDebouncer { + + private static final long DEBOUNCE_DELAY_MS = 300; + + public interface Callback { + void trigger(Size size); + } + + private final Callback callback; + private Size request; + private long deadline; + + private Thread thread; + + public DisplayResizeDebouncer(Callback callback) { + this.callback = callback; + } + + public void start() { + assert thread == null; + thread = new Thread(this::debounce); + thread.setName("debouncer"); + thread.setDaemon(true); + thread.start(); + } + + public void stop() { + if (thread != null) { + thread.interrupt(); + thread = null; + } + } + + private void debounce() { + try { + while (true) { + Size newSize; + synchronized (this) { + while (true) { + long now = SystemClock.uptimeMillis(); + if (request != null && now >= deadline) { + break; + } + if (request == null) { + wait(); + } else { + assert now < deadline; + wait(deadline - now); + } + } + assert request != null : "An active deadline implies request != null"; + newSize = request; + request = null; + } + callback.trigger(newSize); + } + } catch (InterruptedException e) { + // ignore + } finally { + Ln.d("Debouncer thread stopped"); + } + } + + public synchronized void requestResize(Size size) { + assert size != null; + if (request == null) { + deadline = SystemClock.uptimeMillis() + DEBOUNCE_DELAY_MS; + } + request = size; + notify(); + } + + public synchronized void cancelResize() { + request = null; + deadline = 0; + notify(); + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/model/Codec.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/model/Codec.java new file mode 100644 index 0000000..b111710 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/model/Codec.java @@ -0,0 +1,24 @@ +package com.genymobile.scrcpy.model; + +import android.media.MediaCodec; + +public interface Codec { + + enum Type { + VIDEO, + AUDIO, + } + + Type getType(); + + int getId(); + + String getName(); + + String getMimeType(); + + static String getMimeType(MediaCodec codec) { + String[] types = codec.getCodecInfo().getSupportedTypes(); + return types.length > 0 ? types[0] : null; + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/model/CodecOption.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/model/CodecOption.java new file mode 100644 index 0000000..8b356cd --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/model/CodecOption.java @@ -0,0 +1,112 @@ +package com.genymobile.scrcpy.model; + +import java.util.ArrayList; +import java.util.List; + +public class CodecOption { + private final String key; + private final Object value; + + public CodecOption(String key, Object value) { + this.key = key; + this.value = value; + } + + public String getKey() { + return key; + } + + public Object getValue() { + return value; + } + + public static List parse(String codecOptions) { + if (codecOptions.isEmpty()) { + return null; + } + + List result = new ArrayList<>(); + + boolean escape = false; + StringBuilder buf = new StringBuilder(); + + for (char c : codecOptions.toCharArray()) { + switch (c) { + case '\\': + if (escape) { + buf.append('\\'); + escape = false; + } else { + escape = true; + } + break; + case ',': + if (escape) { + buf.append(','); + escape = false; + } else { + // This comma is a separator between codec options + String codecOption = buf.toString(); + result.add(parseOption(codecOption)); + // Clear buf + buf.setLength(0); + } + break; + default: + buf.append(c); + break; + } + } + + if (buf.length() > 0) { + String codecOption = buf.toString(); + result.add(parseOption(codecOption)); + } + + return result; + } + + private static CodecOption parseOption(String option) { + int equalSignIndex = option.indexOf('='); + if (equalSignIndex == -1) { + throw new IllegalArgumentException("'=' expected"); + } + String keyAndType = option.substring(0, equalSignIndex); + if (keyAndType.length() == 0) { + throw new IllegalArgumentException("Key may not be null"); + } + + String key; + String type; + + int colonIndex = keyAndType.indexOf(':'); + if (colonIndex != -1) { + key = keyAndType.substring(0, colonIndex); + type = keyAndType.substring(colonIndex + 1); + } else { + key = keyAndType; + type = "int"; // assume int by default + } + + Object value; + String valueString = option.substring(equalSignIndex + 1); + switch (type) { + case "int": + value = Integer.parseInt(valueString); + break; + case "long": + value = Long.parseLong(valueString); + break; + case "float": + value = Float.parseFloat(valueString); + break; + case "string": + value = valueString; + break; + default: + throw new IllegalArgumentException("Invalid codec option type (int, long, float, str): " + type); + } + + return new CodecOption(key, value); + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/model/ConfigurationException.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/model/ConfigurationException.java new file mode 100644 index 0000000..0e5deaa --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/model/ConfigurationException.java @@ -0,0 +1,7 @@ +package com.genymobile.scrcpy.model; + +public class ConfigurationException extends Exception { + public ConfigurationException(String message) { + super(message); + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/model/DeviceApp.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/model/DeviceApp.java new file mode 100644 index 0000000..60e7e87 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/model/DeviceApp.java @@ -0,0 +1,26 @@ +package com.genymobile.scrcpy.model; + +public final class DeviceApp { + + private final String packageName; + private final String name; + private final boolean system; + + public DeviceApp(String packageName, String name, boolean system) { + this.packageName = packageName; + this.name = name; + this.system = system; + } + + public String getPackageName() { + return packageName; + } + + public String getName() { + return name; + } + + public boolean isSystem() { + return system; + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/model/NewDisplay.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/model/NewDisplay.java new file mode 100644 index 0000000..2544f07 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/model/NewDisplay.java @@ -0,0 +1,31 @@ +package com.genymobile.scrcpy.model; + +public final class NewDisplay { + private Size size; + private int dpi; + + public NewDisplay() { + // Auto size and dpi + } + + public NewDisplay(Size size, int dpi) { + this.size = size; + this.dpi = dpi; + } + + public Size getSize() { + return size; + } + + public int getDpi() { + return dpi; + } + + public boolean hasExplicitSize() { + return size != null; + } + + public boolean hasExplicitDpi() { + return dpi != 0; + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/model/Orientation.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/model/Orientation.java new file mode 100644 index 0000000..8e189d7 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/model/Orientation.java @@ -0,0 +1,54 @@ +package com.genymobile.scrcpy.model; + +public enum Orientation { + + // @formatter:off + Orient0("0"), + Orient90("90"), + Orient180("180"), + Orient270("270"), + Flip0("flip0"), + Flip90("flip90"), + Flip180("flip180"), + Flip270("flip270"); + + public enum Lock { + Unlocked, LockedInitial, LockedValue, + } + + private final String name; + + Orientation(String name) { + this.name = name; + } + + public static Orientation getByName(String name) { + for (Orientation orientation : values()) { + if (orientation.name.equals(name)) { + return orientation; + } + } + + throw new IllegalArgumentException("Unknown orientation: " + name); + } + + public static Orientation fromRotation(int ccwRotation) { + assert ccwRotation >= 0 && ccwRotation < 4; + // Display rotation is expressed counter-clockwise, orientation is expressed clockwise + int cwRotation = (4 - ccwRotation) % 4; + return values()[cwRotation]; + } + + public boolean isFlipped() { + return (ordinal() & 4) != 0; + } + + public int getRotation() { + return ordinal() & 3; + } + + public boolean isSwap() { + // width and height are swapped on 90-degree rotations + return (ordinal() & 1) != 0; + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/model/Point.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/model/Point.java new file mode 100644 index 0000000..8a8458f --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/model/Point.java @@ -0,0 +1,43 @@ +package com.genymobile.scrcpy.model; + +import java.util.Objects; + +public class Point { + private final int x; + private final int y; + + public Point(int x, int y) { + this.x = x; + this.y = y; + } + + public int getX() { + return x; + } + + public int getY() { + return y; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Point point = (Point) o; + return x == point.x && y == point.y; + } + + @Override + public int hashCode() { + return Objects.hash(x, y); + } + + @Override + public String toString() { + return "Point{" + "x=" + x + ", y=" + y + '}'; + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/model/Position.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/model/Position.java new file mode 100644 index 0000000..61fd124 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/model/Position.java @@ -0,0 +1,61 @@ +package com.genymobile.scrcpy.model; + +import java.util.Objects; + +public class Position { + private final Point point; + private final Size screenSize; + + public Position(Point point, Size screenSize) { + this.point = point; + this.screenSize = screenSize; + } + + public Position(int x, int y, int screenWidth, int screenHeight) { + this(new Point(x, y), new Size(screenWidth, screenHeight)); + } + + public Point getPoint() { + return point; + } + + public Size getScreenSize() { + return screenSize; + } + + public Position rotate(int rotation) { + switch (rotation) { + case 1: + return new Position(new Point(screenSize.getHeight() - point.getY(), point.getX()), screenSize.rotate()); + case 2: + return new Position(new Point(screenSize.getWidth() - point.getX(), screenSize.getHeight() - point.getY()), screenSize); + case 3: + return new Position(new Point(point.getY(), screenSize.getWidth() - point.getX()), screenSize.rotate()); + default: + return this; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Position position = (Position) o; + return Objects.equals(point, position.point) && Objects.equals(screenSize, position.screenSize); + } + + @Override + public int hashCode() { + return Objects.hash(point, screenSize); + } + + @Override + public String toString() { + return "Position{" + "point=" + point + ", screenSize=" + screenSize + '}'; + } + +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/model/Size.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/model/Size.java new file mode 100644 index 0000000..5121196 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/model/Size.java @@ -0,0 +1,198 @@ +package com.genymobile.scrcpy.model; + +import com.genymobile.scrcpy.util.BinarySearch; +import com.genymobile.scrcpy.util.Ln; +import com.genymobile.scrcpy.video.VideoConstraints; + +import android.graphics.Rect; +import android.media.MediaCodecInfo; +import android.util.Range; + +import java.util.Objects; + +public final class Size { + private final int width; + private final int height; + + public Size(int width, int height) { + this.width = width; + this.height = height; + } + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + public int getMax() { + return Math.max(width, height); + } + + public Size rotate() { + return new Size(height, width); + } + + public Size constrain(VideoConstraints constraints) { + return constrain(constraints, true); + } + + public Size constrain(VideoConstraints constraints, boolean preserveAspectRatio) { + int maxSize = constraints.getMaxSize(); + int alignment = constraints.getAlignment(); + MediaCodecInfo.VideoCapabilities caps = constraints.getEncoderCapabilities(); + + assert maxSize >= 0 : "Max size may not be negative"; + assert alignment > 0 : "Alignment must be positive"; + assert (alignment & (alignment - 1)) == 0 : "Alignment must be a power-of-two"; + + if (caps == null) { + int w, h; + if (maxSize > 0 && (width > maxSize || height > maxSize)) { + if (preserveAspectRatio) { + if (width > height) { + w = maxSize; + h = height * maxSize / width; + } else { + w = width * maxSize / height; + h = maxSize; + } + } else { + w = Math.min(width, maxSize); + h = Math.min(height, maxSize); + } + } else { + // No constraints + w = width; + h = height; + } + + w = Math.max(align(w, alignment), alignment); + h = Math.max(align(h, alignment), alignment); + + return new Size(w, h); + } + + boolean landscape = width >= height; + int major = landscape ? width : height; + int minor = landscape ? height : width; + + Range majorRange = landscape ? caps.getSupportedWidths() : caps.getSupportedHeights(); + int minMajor = majorRange.getLower(); + int maxMajor = majorRange.getUpper(); + if (maxMajor > major) { + // Never increase the size + maxMajor = major; + } + if (maxSize > 0 && maxMajor > maxSize) { + maxMajor = maxSize; + } + + int minBlock = (minMajor + alignment - 1) / alignment; + int maxBlock = maxMajor / alignment; + + int bestBlock = BinarySearch.findHighestTrue( + minBlock, maxBlock, block -> { + int pixels = block * alignment; + int w = align(width * pixels / major, alignment); + int h = align(height * pixels / major, alignment); + return caps.isSizeSupported(w, h); + }); + + if (bestBlock < minBlock) { + Ln.d("No matching size found, ignore encoder size validation"); + bestBlock = maxBlock; + } + + int bestMajor = bestBlock * alignment; + int bestMinor; + + if (preserveAspectRatio) { + bestMinor = align(minor * bestMajor / major, alignment); + } else { + // The minor dimension can potentially be extended + int maxMinor = landscape ? caps.getSupportedHeightsFor(bestMajor).getUpper() : caps.getSupportedWidthsFor(bestMajor).getUpper(); + if (maxMinor > minor) { + maxMinor = minor; + } + if (maxSize > 0 && maxMinor > maxSize) { + maxMinor = maxSize; + } + bestMinor = align(maxMinor, alignment); + + // The major dimension can potentially be extended + maxMajor = landscape ? caps.getSupportedWidthsFor(bestMinor).getUpper() : caps.getSupportedHeightsFor(bestMinor).getUpper(); + if (maxMajor > major) { + maxMajor = major; + } + if (maxSize > 0 && maxMajor > maxSize) { + maxMajor = maxSize; + } + bestMajor = align(maxMajor, alignment); + } + + minMajor = alignUp(minMajor, alignment); + if (bestMajor < minMajor) { + bestMajor = minMajor; + } + + int minMinor = landscape ? caps.getSupportedHeights().getLower() : caps.getSupportedWidths().getLower(); + minMinor = alignUp(minMinor, alignment); + if (bestMinor < minMinor) { + bestMinor = minMinor; + } + + int w = landscape ? bestMajor : bestMinor; + int h = landscape ? bestMinor : bestMajor; + + assert w % alignment == 0 : "The width must be a multiple of alignment"; + assert h % alignment == 0 : "The height must be a multiple of alignment"; + + return new Size(w, h); + } + + public Size align(int alignment) { + int w = align(width, alignment); + int h = align(height, alignment); + if (w == width && h == height) { + return this; + } + return new Size(w, h); + } + + private static int align(int value, int alignment) { + return value / alignment * alignment; + } + + private static int alignUp(int value, int alignment) { + return (value + alignment - 1) / alignment * alignment; + } + + public Rect toRect() { + return new Rect(0, 0, width, height); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Size size = (Size) o; + return width == size.width && height == size.height; + } + + @Override + public int hashCode() { + return Objects.hash(width, height); + } + + @Override + public String toString() { + return width + "x" + height; + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/opengl/AffineOpenGLFilter.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/opengl/AffineOpenGLFilter.java new file mode 100644 index 0000000..7608a57 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/opengl/AffineOpenGLFilter.java @@ -0,0 +1,135 @@ +package com.genymobile.scrcpy.opengl; + +import com.genymobile.scrcpy.util.AffineMatrix; + +import android.opengl.GLES11Ext; +import android.opengl.GLES20; + +import java.nio.FloatBuffer; + +public class AffineOpenGLFilter implements OpenGLFilter { + + private int program; + private FloatBuffer vertexBuffer; + private FloatBuffer texCoordsBuffer; + private final float[] userMatrix; + + private int vertexPosLoc; + private int texCoordsInLoc; + + private int texLoc; + private int texMatrixLoc; + private int userMatrixLoc; + + public AffineOpenGLFilter(AffineMatrix transform) { + userMatrix = transform.to4x4(); + } + + @Override + public void init() throws OpenGLException { + // @formatter:off + String vertexShaderCode = "#version 100\n" + + "attribute vec4 vertex_pos;\n" + + "attribute vec4 tex_coords_in;\n" + + "varying vec2 tex_coords;\n" + + "uniform mat4 tex_matrix;\n" + + "uniform mat4 user_matrix;\n" + + "void main() {\n" + + " gl_Position = vertex_pos;\n" + + " tex_coords = (tex_matrix * user_matrix * tex_coords_in).xy;\n" + + "}"; + + // @formatter:off + String fragmentShaderCode = "#version 100\n" + + "#extension GL_OES_EGL_image_external : require\n" + + "precision highp float;\n" + + "uniform samplerExternalOES tex;\n" + + "varying vec2 tex_coords;\n" + + "void main() {\n" + + " if (tex_coords.x >= 0.0 && tex_coords.x <= 1.0\n" + + " && tex_coords.y >= 0.0 && tex_coords.y <= 1.0) {\n" + + " gl_FragColor = texture2D(tex, tex_coords);\n" + + " } else {\n" + + " gl_FragColor = vec4(0.0);\n" + + " }\n" + + "}"; + + program = GLUtils.createProgram(vertexShaderCode, fragmentShaderCode); + if (program == 0) { + throw new OpenGLException("Cannot create OpenGL program"); + } + + float[] vertices = { + -1, -1, // Bottom-left + 1, -1, // Bottom-right + -1, 1, // Top-left + 1, 1, // Top-right + }; + + float[] texCoords = { + 0, 0, // Bottom-left + 1, 0, // Bottom-right + 0, 1, // Top-left + 1, 1, // Top-right + }; + + // OpenGL will fill the 3rd and 4th coordinates of the vec4 automatically with 0.0 and 1.0 respectively + vertexBuffer = GLUtils.createFloatBuffer(vertices); + texCoordsBuffer = GLUtils.createFloatBuffer(texCoords); + + vertexPosLoc = GLES20.glGetAttribLocation(program, "vertex_pos"); + assert vertexPosLoc != -1; + + texCoordsInLoc = GLES20.glGetAttribLocation(program, "tex_coords_in"); + assert texCoordsInLoc != -1; + + texLoc = GLES20.glGetUniformLocation(program, "tex"); + assert texLoc != -1; + + texMatrixLoc = GLES20.glGetUniformLocation(program, "tex_matrix"); + assert texMatrixLoc != -1; + + userMatrixLoc = GLES20.glGetUniformLocation(program, "user_matrix"); + assert userMatrixLoc != -1; + } + + @Override + public void draw(int textureId, float[] texMatrix) { + GLES20.glUseProgram(program); + GLUtils.checkGlError(); + + GLES20.glEnableVertexAttribArray(vertexPosLoc); + GLUtils.checkGlError(); + GLES20.glEnableVertexAttribArray(texCoordsInLoc); + GLUtils.checkGlError(); + + GLES20.glVertexAttribPointer(vertexPosLoc, 2, GLES20.GL_FLOAT, false, 0, vertexBuffer); + GLUtils.checkGlError(); + GLES20.glVertexAttribPointer(texCoordsInLoc, 2, GLES20.GL_FLOAT, false, 0, texCoordsBuffer); + GLUtils.checkGlError(); + + GLES20.glActiveTexture(GLES20.GL_TEXTURE0); + GLUtils.checkGlError(); + GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureId); + GLUtils.checkGlError(); + GLES20.glUniform1i(texLoc, 0); + GLUtils.checkGlError(); + + GLES20.glUniformMatrix4fv(texMatrixLoc, 1, false, texMatrix, 0); + GLUtils.checkGlError(); + + GLES20.glUniformMatrix4fv(userMatrixLoc, 1, false, userMatrix, 0); + GLUtils.checkGlError(); + + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); + GLUtils.checkGlError(); + GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); + GLUtils.checkGlError(); + } + + @Override + public void release() { + GLES20.glDeleteProgram(program); + GLUtils.checkGlError(); + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/opengl/GLUtils.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/opengl/GLUtils.java new file mode 100644 index 0000000..72a3f40 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/opengl/GLUtils.java @@ -0,0 +1,124 @@ +package com.genymobile.scrcpy.opengl; + +import com.genymobile.scrcpy.BuildConfig; +import com.genymobile.scrcpy.util.Ln; + +import android.opengl.GLES20; +import android.opengl.GLU; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; + +public final class GLUtils { + + private static final boolean DEBUG = BuildConfig.DEBUG; + + private GLUtils() { + // not instantiable + } + + public static int createProgram(String vertexSource, String fragmentSource) { + int vertexShader = createShader(GLES20.GL_VERTEX_SHADER, vertexSource); + if (vertexShader == 0) { + return 0; + } + + int fragmentShader = createShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource); + if (fragmentShader == 0) { + GLES20.glDeleteShader(vertexShader); + return 0; + } + + int program = GLES20.glCreateProgram(); + if (program == 0) { + GLES20.glDeleteShader(fragmentShader); + GLES20.glDeleteShader(vertexShader); + return 0; + } + + GLES20.glAttachShader(program, vertexShader); + checkGlError(); + GLES20.glAttachShader(program, fragmentShader); + checkGlError(); + GLES20.glLinkProgram(program); + checkGlError(); + + int[] linkStatus = new int[1]; + GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0); + if (linkStatus[0] == 0) { + Ln.e("Could not link program: " + GLES20.glGetProgramInfoLog(program)); + GLES20.glDeleteProgram(program); + GLES20.glDeleteShader(fragmentShader); + GLES20.glDeleteShader(vertexShader); + return 0; + } + + return program; + } + + public static int createShader(int type, String source) { + int shader = GLES20.glCreateShader(type); + if (shader == 0) { + Ln.e(getGlErrorMessage("Could not create shader")); + return 0; + } + + GLES20.glShaderSource(shader, source); + GLES20.glCompileShader(shader); + + int[] compileStatus = new int[1]; + GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compileStatus, 0); + if (compileStatus[0] == 0) { + Ln.e("Could not compile " + getShaderTypeString(type) + ": " + GLES20.glGetShaderInfoLog(shader)); + GLES20.glDeleteShader(shader); + return 0; + } + + return shader; + } + + private static String getShaderTypeString(int type) { + switch (type) { + case GLES20.GL_VERTEX_SHADER: + return "vertex shader"; + case GLES20.GL_FRAGMENT_SHADER: + return "fragment shader"; + default: + return "shader"; + } + } + + /** + * Throws a runtime exception if {@link GLES20#glGetError()} returns an error (useful for debugging). + */ + public static void checkGlError() { + if (DEBUG) { + int error = GLES20.glGetError(); + if (error != GLES20.GL_NO_ERROR) { + throw new RuntimeException(toErrorString(error)); + } + } + } + + public static String getGlErrorMessage(String userError) { + int glError = GLES20.glGetError(); + if (glError == GLES20.GL_NO_ERROR) { + return userError; + } + + return userError + " (" + toErrorString(glError) + ")"; + } + + private static String toErrorString(int glError) { + String errorString = GLU.gluErrorString(glError); + return "glError 0x" + Integer.toHexString(glError) + " " + errorString; + } + + public static FloatBuffer createFloatBuffer(float[] values) { + FloatBuffer fb = ByteBuffer.allocateDirect(values.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer(); + fb.put(values); + fb.position(0); + return fb; + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/opengl/OpenGLException.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/opengl/OpenGLException.java new file mode 100644 index 0000000..cbc9539 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/opengl/OpenGLException.java @@ -0,0 +1,13 @@ +package com.genymobile.scrcpy.opengl; + +import java.io.IOException; + +public class OpenGLException extends IOException { + public OpenGLException(String message) { + super(message); + } + + public OpenGLException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/opengl/OpenGLFilter.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/opengl/OpenGLFilter.java new file mode 100644 index 0000000..6f27777 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/opengl/OpenGLFilter.java @@ -0,0 +1,21 @@ +package com.genymobile.scrcpy.opengl; + +public interface OpenGLFilter { + + /** + * Initialize the OpenGL filter (typically compile the shaders and create the program). + * + * @throws OpenGLException if an initialization error occurs + */ + void init() throws OpenGLException; + + /** + * Render a frame (call for each frame). + */ + void draw(int textureId, float[] texMatrix); + + /** + * Release resources. + */ + void release(); +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/opengl/OpenGLRunner.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/opengl/OpenGLRunner.java new file mode 100644 index 0000000..232f68d --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/opengl/OpenGLRunner.java @@ -0,0 +1,232 @@ +package com.genymobile.scrcpy.opengl; + +import com.genymobile.scrcpy.model.Size; +import com.genymobile.scrcpy.util.Threads; + +import android.graphics.SurfaceTexture; +import android.opengl.EGL14; +import android.opengl.EGLConfig; +import android.opengl.EGLContext; +import android.opengl.EGLDisplay; +import android.opengl.EGLExt; +import android.opengl.EGLSurface; +import android.opengl.GLES11Ext; +import android.opengl.GLES20; +import android.os.Handler; +import android.os.HandlerThread; +import android.view.Surface; + +import java.util.concurrent.Callable; +import java.util.concurrent.Semaphore; + +public final class OpenGLRunner { + + private static HandlerThread handlerThread; + private static Handler handler; + + private EGLDisplay eglDisplay; + private EGLContext eglContext; + private EGLSurface eglSurface; + + private final OpenGLFilter filter; + private final float[] overrideTransformMatrix; + + private SurfaceTexture surfaceTexture; + private Surface inputSurface; + private int textureId; + + private boolean stopped; + + public OpenGLRunner(OpenGLFilter filter, float[] overrideTransformMatrix) { + this.filter = filter; + this.overrideTransformMatrix = overrideTransformMatrix; + } + + public OpenGLRunner(OpenGLFilter filter) { + this(filter, null); + } + + public static synchronized void initOnce() { + if (handlerThread == null) { + handlerThread = new HandlerThread("OpenGLRunner"); + handlerThread.start(); + handler = new Handler(handlerThread.getLooper()); + } + } + + public static void shutdown() throws InterruptedException { + HandlerThread thread; + synchronized (OpenGLRunner.class) { + thread = handlerThread; + } + if (thread != null) { + thread.quitSafely(); + thread.join(); + } + } + + public Surface start(Size inputSize, Size outputSize, Surface outputSurface) throws OpenGLException { + initOnce(); + + // The whole OpenGL execution must be performed on a Handler, so that SurfaceTexture.setOnFrameAvailableListener() works correctly. + // See + try { + Threads.executeSynchronouslyOn(handler, new Callable() { + @Override + public Void call() throws Exception { + run(inputSize, outputSize, outputSurface); + return null; + } + }); + } catch (Throwable throwable) { + if (throwable instanceof OpenGLException) { + throw (OpenGLException) throwable; + } + throw new OpenGLException("Asynchronous OpenGL runner init failed", throwable); + } + + // Synchronization is ok: inputSurface is written before sem.release() and read after sem.acquire() + return inputSurface; + } + + private void run(Size inputSize, Size outputSize, Surface outputSurface) throws OpenGLException { + eglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); + if (eglDisplay == EGL14.EGL_NO_DISPLAY) { + throw new OpenGLException("Unable to get EGL14 display"); + } + + int[] version = new int[2]; + if (!EGL14.eglInitialize(eglDisplay, version, 0, version, 1)) { + throw new OpenGLException("Unable to initialize EGL14"); + } + + // @formatter:off + int[] attribList = { + EGL14.EGL_RED_SIZE, 8, + EGL14.EGL_GREEN_SIZE, 8, + EGL14.EGL_BLUE_SIZE, 8, + EGL14.EGL_ALPHA_SIZE, 8, + EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT, + EGL14.EGL_NONE + }; + + EGLConfig[] configs = new EGLConfig[1]; + int[] numConfigs = new int[1]; + EGL14.eglChooseConfig(eglDisplay, attribList, 0, configs, 0, configs.length, numConfigs, 0); + if (numConfigs[0] <= 0) { + EGL14.eglTerminate(eglDisplay); + throw new OpenGLException("Unable to find ES2 EGL config"); + } + EGLConfig eglConfig = configs[0]; + + // @formatter:off + int[] contextAttribList = { + EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, + EGL14.EGL_NONE + }; + eglContext = EGL14.eglCreateContext(eglDisplay, eglConfig, EGL14.EGL_NO_CONTEXT, contextAttribList, 0); + if (eglContext == null) { + EGL14.eglTerminate(eglDisplay); + throw new OpenGLException("Failed to create EGL context"); + } + + int[] surfaceAttribList = { + EGL14.EGL_NONE + }; + eglSurface = EGL14.eglCreateWindowSurface(eglDisplay, eglConfig, outputSurface, surfaceAttribList, 0); + if (eglSurface == null) { + EGL14.eglDestroyContext(eglDisplay, eglContext); + EGL14.eglTerminate(eglDisplay); + throw new OpenGLException("Failed to create EGL window surface"); + } + + if (!EGL14.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) { + EGL14.eglDestroySurface(eglDisplay, eglSurface); + EGL14.eglDestroyContext(eglDisplay, eglContext); + EGL14.eglTerminate(eglDisplay); + throw new OpenGLException("Failed to make EGL context current"); + } + + int[] textures = new int[1]; + GLES20.glGenTextures(1, textures, 0); + GLUtils.checkGlError(); + textureId = textures[0]; + + GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); + GLUtils.checkGlError(); + GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); + GLUtils.checkGlError(); + GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); + GLUtils.checkGlError(); + GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); + GLUtils.checkGlError(); + + surfaceTexture = new SurfaceTexture(textureId); + surfaceTexture.setDefaultBufferSize(inputSize.getWidth(), inputSize.getHeight()); + inputSurface = new Surface(surfaceTexture); + + filter.init(); + + surfaceTexture.setOnFrameAvailableListener(surfaceTexture -> { + if (stopped) { + // Make sure to never render after resources have been released + return; + } + + render(outputSize); + }, handler); + } + + private void render(Size outputSize) { + GLES20.glViewport(0, 0, outputSize.getWidth(), outputSize.getHeight()); + GLUtils.checkGlError(); + + surfaceTexture.updateTexImage(); + + float[] matrix; + if (overrideTransformMatrix != null) { + matrix = overrideTransformMatrix; + } else { + matrix = new float[16]; + surfaceTexture.getTransformMatrix(matrix); + } + + filter.draw(textureId, matrix); + + EGLExt.eglPresentationTimeANDROID(eglDisplay, eglSurface, surfaceTexture.getTimestamp()); + EGL14.eglSwapBuffers(eglDisplay, eglSurface); + } + + public void stopAndRelease() { + final Semaphore sem = new Semaphore(0); + + handler.post(() -> { + stopped = true; + surfaceTexture.setOnFrameAvailableListener(null, handler); + + filter.release(); + + int[] textures = {textureId}; + GLES20.glDeleteTextures(1, textures, 0); + GLUtils.checkGlError(); + + EGL14.eglDestroySurface(eglDisplay, eglSurface); + EGL14.eglDestroyContext(eglDisplay, eglContext); + EGL14.eglTerminate(eglDisplay); + eglDisplay = EGL14.EGL_NO_DISPLAY; + eglContext = EGL14.EGL_NO_CONTEXT; + eglSurface = EGL14.EGL_NO_SURFACE; + surfaceTexture.release(); + inputSurface.release(); + + sem.release(); + }); + + try { + sem.acquire(); + } catch (InterruptedException e) { + // Behave as if this method call was synchronous + Thread.currentThread().interrupt(); + } + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/util/AffineMatrix.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/util/AffineMatrix.java new file mode 100644 index 0000000..fb507ba --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/util/AffineMatrix.java @@ -0,0 +1,368 @@ +package com.genymobile.scrcpy.util; + +import com.genymobile.scrcpy.model.Point; +import com.genymobile.scrcpy.model.Size; + +/** + * Represents a 2D affine transform (a 3x3 matrix): + * + *

+ *     / a c e \
+ *     | b d f |
+ *     \ 0 0 1 /
+ * 
+ *

+ * Or, a 4x4 matrix if we add a z axis: + * + *

+ *     / a c 0 e \
+ *     | b d 0 f |
+ *     | 0 0 1 0 |
+ *     \ 0 0 0 1 /
+ * 
+ */ +public class AffineMatrix { + + private final double a, b, c, d, e, f; + + /** + * The identity matrix. + */ + public static final AffineMatrix IDENTITY = new AffineMatrix(1, 0, 0, 1, 0, 0); + + /** + * Create a new matrix: + * + *
+     *     / a c e \
+     *     | b d f |
+     *     \ 0 0 1 /
+     * 
+ */ + public AffineMatrix(double a, double b, double c, double d, double e, double f) { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.e = e; + this.f = f; + } + + @Override + public String toString() { + return "[" + a + ", " + c + ", " + e + "; " + b + ", " + d + ", " + f + "]"; + } + + /** + * Return a matrix which converts from Normalized Device Coordinates to pixels. + * + * @param size the target size + * @return the transform matrix + */ + public static AffineMatrix ndcFromPixels(Size size) { + double w = size.getWidth(); + double h = size.getHeight(); + return new AffineMatrix(1 / w, 0, 0, -1 / h, 0, 1); + } + + /** + * Return a matrix which converts from pixels to Normalized Device Coordinates. + * + * @param size the source size + * @return the transform matrix + */ + public static AffineMatrix ndcToPixels(Size size) { + double w = size.getWidth(); + double h = size.getHeight(); + return new AffineMatrix(w, 0, 0, -h, 0, h); + } + + /** + * Apply the transform to a point ({@code this} should be a matrix converted to pixels coordinates via {@link #ndcToPixels(Size)}). + * + * @param point the source point + * @return the converted point + */ + public Point apply(Point point) { + int x = point.getX(); + int y = point.getY(); + int xx = (int) (a * x + c * y + e); + int yy = (int) (b * x + d * y + f); + return new Point(xx, yy); + } + + /** + * Compute this * rhs. + * + * @param rhs the matrix to multiply + * @return the product + */ + public AffineMatrix multiply(AffineMatrix rhs) { + if (rhs == null) { + // For convenience + return this; + } + + double aa = this.a * rhs.a + this.c * rhs.b; + double bb = this.b * rhs.a + this.d * rhs.b; + double cc = this.a * rhs.c + this.c * rhs.d; + double dd = this.b * rhs.c + this.d * rhs.d; + double ee = this.a * rhs.e + this.c * rhs.f + this.e; + double ff = this.b * rhs.e + this.d * rhs.f + this.f; + return new AffineMatrix(aa, bb, cc, dd, ee, ff); + } + + /** + * Multiply all matrices from left to right, ignoring any {@code null} matrix (for convenience). + * + * @param matrices the matrices + * @return the product + */ + public static AffineMatrix multiplyAll(AffineMatrix... matrices) { + AffineMatrix result = null; + for (AffineMatrix matrix : matrices) { + if (result == null) { + result = matrix; + } else { + result = result.multiply(matrix); + } + } + return result; + } + + /** + * Invert the matrix. + * + * @return the inverse matrix (or {@code null} if not invertible). + */ + public AffineMatrix invert() { + // The 3x3 matrix M can be decomposed into M = M1 * M2: + // M1 M2 + // / 1 0 e \ / a c 0 \ + // | 0 1 f | * | b d 0 | + // \ 0 0 1 / \ 0 0 1 / + // + // The inverse of an invertible 2x2 matrix is given by this formula: + // + // / A B \⁻¹ 1 / D -B \ + // \ C D / = ----- \ -C A / + // AD-BC + // + // Let B=c and C=b (to apply the general formula with the same letters). + // + // M⁻¹ = (M1 * M2)⁻¹ = M2⁻¹ * M1⁻¹ + // + // M2⁻¹ M1⁻¹ + // /----------------\ + // 1 / d -B 0 \ / 1 0 -e \ + // = ----- | -C a 0 | * | 0 1 -f | + // ad-BC \ 0 0 1 / \ 0 0 1 / + // + // With the original letters: + // + // 1 / d -c 0 \ / 1 0 -e \ + // M⁻¹ = ----- | -b a 0 | * | 0 1 -f | + // ad-cb \ 0 0 1 / \ 0 0 1 / + // + // 1 / d -c cf-de \ + // = ----- | -b a be-af | + // ad-cb \ 0 0 1 / + + double det = a * d - c * b; + if (det == 0) { + // Not invertible + return null; + } + + double aa = d / det; + double bb = -b / det; + double cc = -c / det; + double dd = a / det; + double ee = (c * f - d * e) / det; + double ff = (b * e - a * f) / det; + + return new AffineMatrix(aa, bb, cc, dd, ee, ff); + } + + /** + * Return this transform applied from the center (0.5, 0.5). + * + * @return the resulting matrix + */ + public AffineMatrix fromCenter() { + return translate(0.5, 0.5).multiply(this).multiply(translate(-0.5, -0.5)); + } + + /** + * Return this transform with the specified aspect ratio. + * + * @param ar the aspect ratio + * @return the resulting matrix + */ + public AffineMatrix withAspectRatio(double ar) { + return scale(1 / ar, 1).multiply(this).multiply(scale(ar, 1)); + } + + /** + * Return this transform with the specified aspect ratio. + * + * @param size the size describing the aspect ratio + * @return the transform + */ + public AffineMatrix withAspectRatio(Size size) { + double ar = (double) size.getWidth() / size.getHeight(); + return withAspectRatio(ar); + } + + /** + * Return a translation matrix. + * + * @param x the horizontal translation + * @param y the vertical translation + * @return the matrix + */ + public static AffineMatrix translate(double x, double y) { + return new AffineMatrix(1, 0, 0, 1, x, y); + } + + /** + * Return a scaling matrix. + * + * @param x the horizontal scaling + * @param y the vertical scaling + * @return the matrix + */ + public static AffineMatrix scale(double x, double y) { + return new AffineMatrix(x, 0, 0, y, 0, 0); + } + + /** + * Return a scaling matrix. + * + * @param from the source size + * @param to the destination size + * @return the matrix + */ + public static AffineMatrix scale(Size from, Size to) { + double scaleX = (double) to.getWidth() / from.getWidth(); + double scaleY = (double) to.getHeight() / from.getHeight(); + return scale(scaleX, scaleY); + } + + /** + * Return a matrix applying a "reframing" (cropping a rectangle). + *

+ * (x, y) is the bottom-left corner, (w, h) is the size of the rectangle. + * + * @param x horizontal coordinate (increasing to the right) + * @param y vertical coordinate (increasing upwards) + * @param w width + * @param h height + * @return the matrix + */ + public static AffineMatrix reframe(double x, double y, double w, double h) { + if (w == 0 || h == 0) { + throw new IllegalArgumentException("Cannot reframe to an empty area: " + w + "x" + h); + } + return scale(1 / w, 1 / h).multiply(translate(-x, -y)); + } + + /** + * Return an orthogonal rotation matrix. + * + * @param ccwRotation the counter-clockwise rotation + * @return the matrix + */ + public static AffineMatrix rotateOrtho(int ccwRotation) { + switch (ccwRotation) { + case 0: + return IDENTITY; + case 1: + // 90° counter-clockwise + return new AffineMatrix(0, 1, -1, 0, 1, 0); + case 2: + // 180° + return new AffineMatrix(-1, 0, 0, -1, 1, 1); + case 3: + // 90° clockwise + return new AffineMatrix(0, -1, 1, 0, 0, 1); + default: + throw new IllegalArgumentException("Invalid rotation: " + ccwRotation); + } + } + + /** + * Return an horizontal flip matrix. + * + * @return the matrix + */ + public static AffineMatrix hflip() { + return new AffineMatrix(-1, 0, 0, 1, 1, 0); + } + + /** + * Return a vertical flip matrix. + * + * @return the matrix + */ + public static AffineMatrix vflip() { + return new AffineMatrix(1, 0, 0, -1, 0, 1); + } + + /** + * Return a rotation matrix. + * + * @param ccwDegrees the angle, in degrees (counter-clockwise) + * @return the matrix + */ + public static AffineMatrix rotate(double ccwDegrees) { + double radians = Math.toRadians(ccwDegrees); + double cos = Math.cos(radians); + double sin = Math.sin(radians); + return new AffineMatrix(cos, sin, -sin, cos, 0, 0); + } + + /** + * Export this affine transform to a 4x4 column-major order matrix. + * + * @param matrix output 4x4 matrix + */ + public void to4x4(float[] matrix) { + // matrix is a 4x4 matrix in column-major order + + // Column 0 + matrix[0] = (float) a; + matrix[1] = (float) b; + matrix[2] = 0; + matrix[3] = 0; + + // Column 1 + matrix[4] = (float) c; + matrix[5] = (float) d; + matrix[6] = 0; + matrix[7] = 0; + + // Column 2 + matrix[8] = 0; + matrix[9] = 0; + matrix[10] = 1; + matrix[11] = 0; + + // Column 3 + matrix[12] = (float) e; + matrix[13] = (float) f; + matrix[14] = 0; + matrix[15] = 1; + } + + /** + * Export this affine transform to a 4x4 column-major order matrix. + * + * @return 4x4 matrix + */ + public float[] to4x4() { + float[] matrix = new float[16]; + to4x4(matrix); + return matrix; + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/util/Binary.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/util/Binary.java new file mode 100644 index 0000000..f46ba69 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/util/Binary.java @@ -0,0 +1,38 @@ +package com.genymobile.scrcpy.util; + +public final class Binary { + private Binary() { + // not instantiable + } + + public static int toUnsigned(short value) { + return value & 0xffff; + } + + public static int toUnsigned(byte value) { + return value & 0xff; + } + + /** + * Convert unsigned 16-bit fixed-point to a float between 0 and 1 + * + * @param value encoded value + * @return Float value between 0 and 1 + */ + public static float u16FixedPointToFloat(short value) { + int unsignedShort = Binary.toUnsigned(value); + // 0x1p16f is 2^16 as float + return unsignedShort == 0xffff ? 1f : (unsignedShort / 0x1p16f); + } + + /** + * Convert signed 16-bit fixed-point to a float between -1 and 1 + * + * @param value encoded value + * @return Float value between -1 and 1 + */ + public static float i16FixedPointToFloat(short value) { + // 0x1p15f is 2^15 as float + return value == 0x7fff ? 1f : (value / 0x1p15f); + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/util/BinarySearch.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/util/BinarySearch.java new file mode 100644 index 0000000..381601f --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/util/BinarySearch.java @@ -0,0 +1,53 @@ +package com.genymobile.scrcpy.util; + +import com.genymobile.scrcpy.AndroidVersions; + +import android.annotation.TargetApi; + +import java.util.function.Predicate; + +public final class BinarySearch { + private BinarySearch() { + // not instantiable + } + + /** + * Find the highest value for which the predicate is true. + *

+ * This assumes that the predicate is monotonic: + * - if it returns {@code false} for a given value, it must return {@code false} for higher values, or equivalently + * - if it returns {@code true} for a given value, it must return {@code true} for lower values. + * + * @param low the lowest value + * @param high the highest value + * @param predicate the predicate + * @return the highest value for which {@code predicate.test(value)} is {@code true}, or {@code low - 1} if not found. + */ + // This function is used for virtual displays, so we don't need to support older versions anyway + @TargetApi(AndroidVersions.API_24_ANDROID_7_0) // for Predicate + public static int findHighestTrue(int low, int high, Predicate predicate) { + if (low <= high) { + // Fast path + if (predicate.test(high)) { + return high; + } + --high; + } + + int result = low - 1; // low-1 means "not found" + while (low <= high) { + int mid = low + (high - low + 1) / 2; + if (predicate.test(mid)) { + result = mid; + + // predicate holds, go right + low = mid + 1; + } else { + // predicate false, go left + high = mid - 1; + } + } + + return result; + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/util/CodecUtils.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/util/CodecUtils.java new file mode 100644 index 0000000..3a01256 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/util/CodecUtils.java @@ -0,0 +1,38 @@ +package com.genymobile.scrcpy.util; + +import android.media.MediaCodecInfo; +import android.media.MediaCodecList; +import android.media.MediaFormat; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public final class CodecUtils { + + private CodecUtils() { + // not instantiable + } + + public static void setCodecOption(MediaFormat format, String key, Object value) { + if (value instanceof Integer) { + format.setInteger(key, (Integer) value); + } else if (value instanceof Long) { + format.setLong(key, (Long) value); + } else if (value instanceof Float) { + format.setFloat(key, (Float) value); + } else if (value instanceof String) { + format.setString(key, (String) value); + } + } + + public static MediaCodecInfo[] getEncoders(MediaCodecList codecs, String mimeType) { + List result = new ArrayList<>(); + for (MediaCodecInfo codecInfo : codecs.getCodecInfos()) { + if (codecInfo.isEncoder() && Arrays.asList(codecInfo.getSupportedTypes()).contains(mimeType)) { + result.add(codecInfo); + } + } + return result.toArray(new MediaCodecInfo[result.size()]); + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/util/Command.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/util/Command.java new file mode 100644 index 0000000..b26158e --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/util/Command.java @@ -0,0 +1,43 @@ +package com.genymobile.scrcpy.util; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Scanner; + +public final class Command { + private Command() { + // not instantiable + } + + public static void exec(String... cmd) throws IOException, InterruptedException { + Process process = Runtime.getRuntime().exec(cmd); + int exitCode = process.waitFor(); + if (exitCode != 0) { + throw new IOException("Command " + Arrays.toString(cmd) + " returned with value " + exitCode); + } + } + + public static String execReadLine(String... cmd) throws IOException, InterruptedException { + String result = null; + Process process = Runtime.getRuntime().exec(cmd); + Scanner scanner = new Scanner(process.getInputStream()); + if (scanner.hasNextLine()) { + result = scanner.nextLine(); + } + int exitCode = process.waitFor(); + if (exitCode != 0) { + throw new IOException("Command " + Arrays.toString(cmd) + " returned with value " + exitCode); + } + return result; + } + + public static String execReadOutput(String... cmd) throws IOException, InterruptedException { + Process process = Runtime.getRuntime().exec(cmd); + String output = IO.toString(process.getInputStream()); + int exitCode = process.waitFor(); + if (exitCode != 0) { + throw new IOException("Command " + Arrays.toString(cmd) + " returned with value " + exitCode); + } + return output; + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/util/HandlerExecutor.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/util/HandlerExecutor.java new file mode 100644 index 0000000..0330998 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/util/HandlerExecutor.java @@ -0,0 +1,23 @@ +package com.genymobile.scrcpy.util; + +import android.os.Handler; + +import java.util.concurrent.Executor; +import java.util.concurrent.RejectedExecutionException; + +// Inspired from hidden android.os.HandlerExecutor + +public class HandlerExecutor implements Executor { + private final Handler handler; + + public HandlerExecutor(Handler handler) { + this.handler = handler; + } + + @Override + public void execute(Runnable command) { + if (!handler.post(command)) { + throw new RejectedExecutionException(handler + " is shutting down"); + } + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/util/IO.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/util/IO.java new file mode 100644 index 0000000..16ddaed --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/util/IO.java @@ -0,0 +1,79 @@ +package com.genymobile.scrcpy.util; + +import com.genymobile.scrcpy.AndroidVersions; +import com.genymobile.scrcpy.BuildConfig; + +import android.os.Build; +import android.system.ErrnoException; +import android.system.Os; +import android.system.OsConstants; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.util.Scanner; + +public final class IO { + private IO() { + // not instantiable + } + + private static int write(FileDescriptor fd, ByteBuffer from) throws IOException { + while (true) { + try { + return Os.write(fd, from); + } catch (ErrnoException e) { + if (e.errno != OsConstants.EINTR) { + throw new IOException(e); + } + } + } + } + + public static void writeFully(FileDescriptor fd, ByteBuffer from) throws IOException { + if (Build.VERSION.SDK_INT >= AndroidVersions.API_23_ANDROID_6_0) { + while (from.hasRemaining()) { + write(fd, from); + } + } else { + // ByteBuffer position is not updated as expected by Os.write() on old Android versions, so + // handle the position and the remaining bytes manually. + // See . + int position = from.position(); + int remaining = from.remaining(); + while (remaining > 0) { + int w = write(fd, from); + if (BuildConfig.DEBUG && w < 0) { + // w should not be negative, since an exception is thrown on error + throw new AssertionError("Os.write() returned a negative value (" + w + ")"); + } + remaining -= w; + position += w; + from.position(position); + } + } + } + + public static void writeFully(FileDescriptor fd, byte[] buffer, int offset, int len) throws IOException { + writeFully(fd, ByteBuffer.wrap(buffer, offset, len)); + } + + public static String toString(InputStream inputStream) { + StringBuilder builder = new StringBuilder(); + Scanner scanner = new Scanner(inputStream); + while (scanner.hasNextLine()) { + builder.append(scanner.nextLine()).append('\n'); + } + return builder.toString(); + } + + public static boolean isBrokenPipe(IOException e) { + Throwable cause = e.getCause(); + return cause instanceof ErrnoException && ((ErrnoException) cause).errno == OsConstants.EPIPE; + } + + public static boolean isBrokenPipe(Exception e) { + return e instanceof IOException && isBrokenPipe((IOException) e); + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/util/Ln.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/util/Ln.java new file mode 100644 index 0000000..811a4f3 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/util/Ln.java @@ -0,0 +1,122 @@ +package com.genymobile.scrcpy.util; + +import android.util.Log; + +import java.io.FileDescriptor; +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.io.PrintStream; + +/** + * Log both to Android logger (so that logs are visible in "adb logcat") and standard output/error (so that they are visible in the terminal + * directly). + */ +public final class Ln { + + private static final String TAG = "scrcpy"; + private static final String PREFIX = "[server] "; + + private static final PrintStream CONSOLE_OUT = new PrintStream(new FileOutputStream(FileDescriptor.out)); + private static final PrintStream CONSOLE_ERR = new PrintStream(new FileOutputStream(FileDescriptor.err)); + + public enum Level { + VERBOSE, DEBUG, INFO, WARN, ERROR + } + + private static Level threshold = Level.INFO; + + private Ln() { + // not instantiable + } + + public static void disableSystemStreams() { + PrintStream nullStream = new PrintStream(new NullOutputStream()); + System.setOut(nullStream); + System.setErr(nullStream); + } + + /** + * Initialize the log level. + *

+ * Must be called before starting any new thread. + * + * @param level the log level + */ + public static void initLogLevel(Level level) { + threshold = level; + } + + public static boolean isEnabled(Level level) { + return level.ordinal() >= threshold.ordinal(); + } + + public static void v(String message) { + if (isEnabled(Level.VERBOSE)) { + Log.v(TAG, message); + CONSOLE_OUT.print(PREFIX + "VERBOSE: " + message + '\n'); + } + } + + public static void d(String message) { + if (isEnabled(Level.DEBUG)) { + Log.d(TAG, message); + CONSOLE_OUT.print(PREFIX + "DEBUG: " + message + '\n'); + } + } + + public static void i(String message) { + if (isEnabled(Level.INFO)) { + Log.i(TAG, message); + CONSOLE_OUT.print(PREFIX + "INFO: " + message + '\n'); + } + } + + public static void w(String message, Throwable throwable) { + if (isEnabled(Level.WARN)) { + Log.w(TAG, message, throwable); + synchronized (CONSOLE_ERR) { + CONSOLE_ERR.print(PREFIX + "WARN: " + message + '\n'); + if (throwable != null) { + throwable.printStackTrace(CONSOLE_ERR); + } + } + } + } + + public static void w(String message) { + w(message, null); + } + + public static void e(String message, Throwable throwable) { + if (isEnabled(Level.ERROR)) { + Log.e(TAG, message, throwable); + synchronized (CONSOLE_ERR) { + CONSOLE_ERR.print(PREFIX + "ERROR: " + message + '\n'); + if (throwable != null) { + throwable.printStackTrace(CONSOLE_ERR); + } + } + } + } + + public static void e(String message) { + e(message, null); + } + + static class NullOutputStream extends OutputStream { + @Override + public void write(byte[] b) { + // ignore + } + + @Override + public void write(byte[] b, int off, int len) { + // ignore + } + + @Override + public void write(int b) { + // ignore + } + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/util/LogUtils.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/util/LogUtils.java new file mode 100644 index 0000000..4f4605e --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/util/LogUtils.java @@ -0,0 +1,299 @@ +package com.genymobile.scrcpy.util; + +import com.genymobile.scrcpy.AndroidVersions; +import com.genymobile.scrcpy.audio.AudioCodec; +import com.genymobile.scrcpy.device.Device; +import com.genymobile.scrcpy.display.DisplayInfo; +import com.genymobile.scrcpy.model.Codec; +import com.genymobile.scrcpy.model.DeviceApp; +import com.genymobile.scrcpy.model.Size; +import com.genymobile.scrcpy.video.VideoCodec; +import com.genymobile.scrcpy.wrappers.DisplayManager; +import com.genymobile.scrcpy.wrappers.ServiceManager; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.graphics.Rect; +import android.hardware.camera2.CameraAccessException; +import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.CameraManager; +import android.hardware.camera2.params.StreamConfigurationMap; +import android.media.MediaCodec; +import android.media.MediaCodecInfo; +import android.media.MediaCodecList; +import android.os.Build; +import android.util.Range; + +import java.text.DecimalFormat; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.SortedSet; +import java.util.TreeSet; + +public final class LogUtils { + + private LogUtils() { + // not instantiable + } + + private static String buildEncoderListMessage(String type, Codec[] codecs) { + StringBuilder builder = new StringBuilder("List of ").append(type).append(" encoders:"); + MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS); + for (Codec codec : codecs) { + MediaCodecInfo[] encoders = CodecUtils.getEncoders(codecList, codec.getMimeType()); + for (MediaCodecInfo info : encoders) { + int lineStart = builder.length(); + builder.append("\n --").append(type).append("-codec=").append(codec.getName()); + builder.append(" --").append(type).append("-encoder=").append(info.getName()); + if (Build.VERSION.SDK_INT >= AndroidVersions.API_29_ANDROID_10) { + int lineLength = builder.length() - lineStart; + final int column = 70; + if (lineLength < column) { + int padding = column - lineLength; + builder.append(String.format("%" + padding + "s", " ")); + } + builder.append(" (").append(getHwCodecType(info)).append(')'); + if (info.isVendor()) { + builder.append(" [vendor]"); + } + if (info.isAlias()) { + builder.append(" (alias for ").append(info.getCanonicalName()).append(')'); + } + } + + } + } + + return builder.toString(); + } + + public static String buildVideoEncoderListMessage() { + return buildEncoderListMessage("video", VideoCodec.values()); + } + + public static String buildAudioEncoderListMessage() { + return buildEncoderListMessage("audio", AudioCodec.values()); + } + + @TargetApi(AndroidVersions.API_29_ANDROID_10) + private static String getHwCodecType(MediaCodecInfo info) { + if (info.isSoftwareOnly()) { + return "sw"; + } + if (info.isHardwareAccelerated()) { + return "hw"; + } + return "hybrid"; + } + + public static String buildDisplayListMessage() { + StringBuilder builder = new StringBuilder("List of displays:"); + DisplayManager displayManager = ServiceManager.getDisplayManager(); + int[] displayIds = displayManager.getDisplayIds(); + if (displayIds == null || displayIds.length == 0) { + builder.append("\n (none)"); + } else { + for (int id : displayIds) { + builder.append("\n --display-id=").append(id).append(" ("); + DisplayInfo displayInfo = displayManager.getDisplayInfo(id); + if (displayInfo != null) { + Size size = displayInfo.getSize(); + builder.append(size.getWidth()).append("x").append(size.getHeight()); + } else { + builder.append("size unknown"); + } + builder.append(")"); + } + } + return builder.toString(); + } + + private static String getCameraFacingName(int facing) { + switch (facing) { + case CameraCharacteristics.LENS_FACING_FRONT: + return "front"; + case CameraCharacteristics.LENS_FACING_BACK: + return "back"; + case CameraCharacteristics.LENS_FACING_EXTERNAL: + return "external"; + default: + return "unknown"; + } + } + + private static boolean isCameraBackwardCompatible(CameraCharacteristics characteristics) { + int[] capabilities = characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES); + if (capabilities == null) { + return false; + } + + for (int capability : capabilities) { + if (capability == CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE) { + return true; + } + } + + return false; + } + + public static String buildCameraListMessage(boolean includeSizes) { + StringBuilder builder = new StringBuilder("List of cameras:"); + CameraManager cameraManager = ServiceManager.getCameraManager(); + try { + String[] cameraIds = cameraManager.getCameraIdList(); + if (cameraIds.length == 0) { + builder.append("\n (none)"); + } else { + for (String id : cameraIds) { + CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(id); + + if (!isCameraBackwardCompatible(characteristics)) { + // Ignore depth cameras as suggested by official documentation + // + continue; + } + + builder.append("\n --camera-id=").append(id); + + int facing = characteristics.get(CameraCharacteristics.LENS_FACING); + builder.append(" (").append(getCameraFacingName(facing)).append(", "); + + Rect activeSize = characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE); + builder.append(activeSize.width()).append("x").append(activeSize.height()); + + try { + // Capture frame rates for low-FPS mode are the same for every resolution + Range[] lowFpsRanges = characteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES); + if (lowFpsRanges != null) { + String uniqueLowFps = getFormattedUniqueSet(lowFpsRanges); + builder.append(", fps=").append(uniqueLowFps); + } + } catch (Exception e) { + // Some devices may provide invalid ranges, causing an IllegalArgumentException "lower must be less than or equal to upper" + Ln.w("Could not get available frame rates for camera " + id, e); + } + + if (Build.VERSION.SDK_INT >= AndroidVersions.API_30_ANDROID_11) { + try { + Range zoomRange = characteristics.get(CameraCharacteristics.CONTROL_ZOOM_RATIO_RANGE); + if (zoomRange != null) { + String zoom = getFormattedZoomRange(zoomRange); + builder.append(", zoom-range=").append(zoom); + } + } catch (Exception e) { + Ln.w("Could not get available zoom ranges for camera " + id, e); + } + } + + builder.append(')'); + + if (includeSizes) { + StreamConfigurationMap configs = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); + + android.util.Size[] sizes = configs.getOutputSizes(MediaCodec.class); + if (sizes == null || sizes.length == 0) { + builder.append("\n (none)"); + } else { + for (android.util.Size size : sizes) { + builder.append("\n - ").append(size.getWidth()).append('x').append(size.getHeight()); + } + } + + android.util.Size[] highSpeedSizes = configs.getHighSpeedVideoSizes(); + if (highSpeedSizes != null && highSpeedSizes.length > 0) { + builder.append("\n High speed capture (--camera-high-speed):"); + for (android.util.Size size : highSpeedSizes) { + Range[] highFpsRanges = configs.getHighSpeedVideoFpsRanges(); + String uniqueHighFps = getFormattedUniqueSet(highFpsRanges); + builder.append("\n - ").append(size.getWidth()).append("x").append(size.getHeight()); + builder.append(" (fps=").append(uniqueHighFps).append(')'); + } + } + } + } + } + } catch (CameraAccessException e) { + builder.append("\n (access denied)"); + } + return builder.toString(); + } + + private static String getFormattedUniqueSet(Range[] ranges) { + SortedSet set = new TreeSet<>(); + for (Range range : ranges) { + set.add(range.getUpper()); + } + + StringBuilder builder = new StringBuilder("{"); + boolean first = true; + for (Integer i : set) { + if (!first) { + builder.append(", "); + } else { + first = false; + } + builder.append(i); + } + builder.append("}"); + + return builder.toString(); + } + + private static String getFormattedZoomRange(Range range) { + DecimalFormat format = new DecimalFormat("#.##"); + return "[" + format.format(range.getLower()) + ", " + format.format(range.getUpper()) + "]"; + } + + public static String buildAppListMessage() { + List apps = Device.listApps(); + return buildAppListMessage("List of apps:", apps); + } + + @SuppressLint("QueryPermissionsNeeded") + public static String buildAppListMessage(String title, List apps) { + StringBuilder builder = new StringBuilder(title); + + // Sort by: + // 1. system flag (system apps are before non-system apps) + // 2. name + // 3. package name + // Comparator.comparing() was introduced in API 24, so it cannot be used here to simplify the code + Collections.sort(apps, (thisApp, otherApp) -> { + // System apps first + int cmp = -Boolean.compare(thisApp.isSystem(), otherApp.isSystem()); + if (cmp != 0) { + return cmp; + } + + cmp = Objects.compare(thisApp.getName(), otherApp.getName(), String::compareTo); + if (cmp != 0) { + return cmp; + } + + return Objects.compare(thisApp.getPackageName(), otherApp.getPackageName(), String::compareTo); + }); + + final int column = 30; + for (DeviceApp app : apps) { + String name = app.getName(); + int padding = column - name.length(); + builder.append("\n "); + if (app.isSystem()) { + builder.append("* "); + } else { + builder.append("- "); + + } + builder.append(name); + if (padding > 0) { + builder.append(String.format("%" + padding + "s", " ")); + } else { + builder.append("\n ").append(String.format("%" + column + "s", " ")); + } + builder.append(" ").append(app.getPackageName()); + } + + return builder.toString(); + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/util/Settings.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/util/Settings.java new file mode 100644 index 0000000..4eb67d1 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/util/Settings.java @@ -0,0 +1,38 @@ +package com.genymobile.scrcpy.util; + +import com.genymobile.scrcpy.wrappers.ContentProvider; +import com.genymobile.scrcpy.wrappers.ServiceManager; + +public final class Settings { + + public static final String TABLE_SYSTEM = ContentProvider.TABLE_SYSTEM; + public static final String TABLE_SECURE = ContentProvider.TABLE_SECURE; + public static final String TABLE_GLOBAL = ContentProvider.TABLE_GLOBAL; + + private Settings() { + /* not instantiable */ + } + + public static String getValue(String table, String key) throws SettingsException { + try (ContentProvider provider = ServiceManager.getActivityManager().createSettingsProvider()) { + return provider.getValue(table, key); + } + } + + public static void putValue(String table, String key, String value) throws SettingsException { + try (ContentProvider provider = ServiceManager.getActivityManager().createSettingsProvider()) { + provider.putValue(table, key, value); + } + + } + + public static String getAndPutValue(String table, String key, String value) throws SettingsException { + try (ContentProvider provider = ServiceManager.getActivityManager().createSettingsProvider()) { + String oldValue = provider.getValue(table, key); + if (!value.equals(oldValue)) { + provider.putValue(table, key, value); + } + return oldValue; + } + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/util/SettingsException.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/util/SettingsException.java new file mode 100644 index 0000000..87fa388 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/util/SettingsException.java @@ -0,0 +1,11 @@ +package com.genymobile.scrcpy.util; + +public class SettingsException extends Exception { + private static String createMessage(String method, String table, String key, String value) { + return "Could not access settings: " + method + " " + table + " " + key + (value != null ? " " + value : ""); + } + + public SettingsException(String method, String table, String key, String value, Throwable cause) { + super(createMessage(method, table, key, value), cause); + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/util/StringUtils.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/util/StringUtils.java new file mode 100644 index 0000000..8b19ca3 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/util/StringUtils.java @@ -0,0 +1,22 @@ +package com.genymobile.scrcpy.util; + +public final class StringUtils { + private StringUtils() { + // not instantiable + } + + public static int getUtf8TruncationIndex(byte[] utf8, int maxLength) { + int len = utf8.length; + if (len <= maxLength) { + return len; + } + len = maxLength; + // see UTF-8 encoding + while ((utf8[len] & 0x80) != 0 && (utf8[len] & 0xc0) != 0xc0) { + // the next byte is not the start of a new UTF-8 codepoint + // so if we would cut there, the character would be truncated + len--; + } + return len; + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/util/Threads.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/util/Threads.java new file mode 100644 index 0000000..c561d1e --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/util/Threads.java @@ -0,0 +1,43 @@ +package com.genymobile.scrcpy.util; + +import android.os.Handler; + +import java.util.concurrent.Callable; +import java.util.concurrent.Semaphore; + +public final class Threads { + private Threads() { + // not instantiable + } + + public static T executeSynchronouslyOn(Handler handler, Callable callable) throws Throwable { + // Simulate CompletableFuture, but working for all Android versions + final Semaphore sem = new Semaphore(0); + @SuppressWarnings("unchecked") + T[] resultRef = (T[]) new Object[1]; + Throwable[] throwableRef = new Throwable[1]; + + handler.post(() -> { + try { + resultRef[0] = callable.call(); + } catch (Throwable throwable) { + throwableRef[0] = throwable; + } finally { + sem.release(); + } + }); + + try { + sem.acquire(); + } catch (InterruptedException e) { + // Behave as if this method call was synchronous + Thread.currentThread().interrupt(); + } + + if (throwableRef[0] != null) { + throw throwableRef[0]; + } + + return resultRef[0]; + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/video/CameraAspectRatio.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/video/CameraAspectRatio.java new file mode 100644 index 0000000..bf1cba5 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/video/CameraAspectRatio.java @@ -0,0 +1,37 @@ +package com.genymobile.scrcpy.video; + +public final class CameraAspectRatio { + private static final float SENSOR = -1; + + private float ar; + + private CameraAspectRatio(float ar) { + this.ar = ar; + } + + public static CameraAspectRatio fromFloat(float ar) { + if (ar < 0) { + throw new IllegalArgumentException("Invalid aspect ratio: " + ar); + } + return new CameraAspectRatio(ar); + } + + public static CameraAspectRatio fromFraction(int w, int h) { + if (w <= 0 || h <= 0) { + throw new IllegalArgumentException("Invalid aspect ratio: " + w + ":" + h); + } + return new CameraAspectRatio((float) w / h); + } + + public static CameraAspectRatio sensorAspectRatio() { + return new CameraAspectRatio(SENSOR); + } + + public boolean isSensor() { + return ar == SENSOR; + } + + public float getAspectRatio() { + return ar; + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/video/CameraCapture.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/video/CameraCapture.java new file mode 100644 index 0000000..1aaeafa --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/video/CameraCapture.java @@ -0,0 +1,531 @@ +package com.genymobile.scrcpy.video; + +import com.genymobile.scrcpy.AndroidVersions; +import com.genymobile.scrcpy.Options; +import com.genymobile.scrcpy.model.ConfigurationException; +import com.genymobile.scrcpy.model.Orientation; +import com.genymobile.scrcpy.model.Size; +import com.genymobile.scrcpy.opengl.AffineOpenGLFilter; +import com.genymobile.scrcpy.opengl.OpenGLFilter; +import com.genymobile.scrcpy.opengl.OpenGLRunner; +import com.genymobile.scrcpy.util.AffineMatrix; +import com.genymobile.scrcpy.util.HandlerExecutor; +import com.genymobile.scrcpy.util.Ln; +import com.genymobile.scrcpy.util.LogUtils; +import com.genymobile.scrcpy.wrappers.ServiceManager; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.graphics.Rect; +import android.hardware.camera2.CameraAccessException; +import android.hardware.camera2.CameraCaptureSession; +import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession; +import android.hardware.camera2.CameraDevice; +import android.hardware.camera2.CameraManager; +import android.hardware.camera2.CaptureFailure; +import android.hardware.camera2.CaptureRequest; +import android.hardware.camera2.params.OutputConfiguration; +import android.hardware.camera2.params.SessionConfiguration; +import android.hardware.camera2.params.StreamConfigurationMap; +import android.media.MediaCodec; +import android.os.Handler; +import android.os.HandlerThread; +import android.util.Range; +import android.view.Surface; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Stream; + +public class CameraCapture extends SurfaceCapture { + + public static final float[] VFLIP_MATRIX = { + 1, 0, 0, 0, // column 1 + 0, -1, 0, 0, // column 2 + 0, 0, 1, 0, // column 3 + 0, 1, 0, 1, // column 4 + }; + + private static final float ZOOM_FACTOR = 1 + 1 / 16f; + + private final String explicitCameraId; + private final CameraFacing cameraFacing; + private final Size explicitSize; + private final CameraAspectRatio aspectRatio; + private final int fps; + private final boolean highSpeed; + private final Rect crop; + private final Orientation captureOrientation; + private final float angle; + private final boolean initialTorch; + private float zoom; + + private VideoConstraints videoConstraints; + + private String cameraId; + private Size captureSize; + private Size videoSize; // after OpenGL transforms + private Range zoomRange; + + private AffineMatrix transform; + private OpenGLRunner glRunner; + + private HandlerThread cameraThread; + private Handler cameraHandler; + private CameraDevice cameraDevice; + private Executor cameraExecutor; + + private final AtomicBoolean disconnected = new AtomicBoolean(); + + // The following fields must be accessed only from the camera thread + private boolean started; + private CaptureRequest.Builder requestBuilder; + private CameraCaptureSession currentSession; + + public CameraCapture(Options options) { + this.explicitCameraId = options.getCameraId(); + this.cameraFacing = options.getCameraFacing(); + this.explicitSize = options.getCameraSize(); + this.aspectRatio = options.getCameraAspectRatio(); + this.fps = options.getCameraFps(); + this.highSpeed = options.getCameraHighSpeed(); + this.crop = options.getCrop(); + this.captureOrientation = options.getCaptureOrientation(); + assert captureOrientation != null; + this.angle = options.getAngle(); + this.initialTorch = options.getCameraTorch(); + this.zoom = options.getCameraZoom(); + } + + @Override + protected void init(VideoConstraints videoConstraints) throws ConfigurationException, IOException { + this.videoConstraints = videoConstraints; + + cameraThread = new HandlerThread("camera"); + cameraThread.start(); + cameraHandler = new Handler(cameraThread.getLooper()); + cameraExecutor = new HandlerExecutor(cameraHandler); + + try { + cameraId = selectCamera(explicitCameraId, cameraFacing); + if (cameraId == null) { + throw new ConfigurationException("No matching camera found"); + } + + Ln.i("Using camera '" + cameraId + "'"); + cameraDevice = openCamera(cameraId); + } catch (CameraAccessException | InterruptedException e) { + throw new IOException(e); + } + } + + @Override + public void prepare() throws IOException { + try { + int maxSize = videoConstraints.getMaxSize(); + captureSize = selectSize(cameraId, explicitSize, maxSize, aspectRatio, highSpeed); + if (captureSize == null) { + throw new IOException("Could not select camera size"); + } + } catch (CameraAccessException e) { + throw new IOException(e); + } + + VideoFilter filter = new VideoFilter(captureSize); + + if (crop != null) { + filter.addCrop(crop, false); + } + + if (captureOrientation != Orientation.Orient0) { + filter.addOrientation(captureOrientation); + } + + filter.addAngle(angle); + + transform = filter.getInverseTransform(); + videoSize = filter.getOutputSize().align(videoConstraints.getAlignment()); + + if (!videoSize.equals(captureSize) && transform == null) { + // The camera stream can only be rendered on a surface matching the input size. + // If the video constraints change the size, an intermediate OpenGL filter is required. + transform = AffineMatrix.IDENTITY; + } + } + + private static String selectCamera(String explicitCameraId, CameraFacing cameraFacing) throws CameraAccessException, ConfigurationException { + CameraManager cameraManager = ServiceManager.getCameraManager(); + + String[] cameraIds = cameraManager.getCameraIdList(); + if (explicitCameraId != null) { + if (!Arrays.asList(cameraIds).contains(explicitCameraId)) { + Ln.e("Camera with id " + explicitCameraId + " not found\n" + LogUtils.buildCameraListMessage(false)); + throw new ConfigurationException("Camera id not found"); + } + return explicitCameraId; + } + + if (cameraFacing == null) { + // Use the first one + return cameraIds.length > 0 ? cameraIds[0] : null; + } + + for (String cameraId : cameraIds) { + CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraId); + + int facing = characteristics.get(CameraCharacteristics.LENS_FACING); + if (cameraFacing.value() == facing) { + return cameraId; + } + } + + // Not found + return null; + } + + @TargetApi(AndroidVersions.API_24_ANDROID_7_0) + private static Size selectSize(String cameraId, Size explicitSize, int maxSize, CameraAspectRatio aspectRatio, boolean highSpeed) + throws CameraAccessException { + if (explicitSize != null) { + return explicitSize; + } + + CameraManager cameraManager = ServiceManager.getCameraManager(); + CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraId); + + StreamConfigurationMap configs = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); + android.util.Size[] sizes = highSpeed ? configs.getHighSpeedVideoSizes() : configs.getOutputSizes(MediaCodec.class); + if (sizes == null) { + return null; + } + + Stream stream = Arrays.stream(sizes); + if (maxSize > 0) { + stream = stream.filter(it -> it.getWidth() <= maxSize && it.getHeight() <= maxSize); + } + + Float targetAspectRatio = resolveAspectRatio(aspectRatio, characteristics); + if (targetAspectRatio != null) { + stream = stream.filter(it -> { + float ar = ((float) it.getWidth() / it.getHeight()); + float arRatio = ar / targetAspectRatio; + // Accept if the aspect ratio is the target aspect ratio + or - 10% + return arRatio >= 0.9f && arRatio <= 1.1f; + }); + } + + Optional selected = stream.max((s1, s2) -> { + // Greater width is better + int cmp = Integer.compare(s1.getWidth(), s2.getWidth()); + if (cmp != 0) { + return cmp; + } + + if (targetAspectRatio != null) { + // Closer to the target aspect ratio is better + float ar1 = ((float) s1.getWidth() / s1.getHeight()); + float arRatio1 = ar1 / targetAspectRatio; + float distance1 = Math.abs(1 - arRatio1); + + float ar2 = ((float) s2.getWidth() / s2.getHeight()); + float arRatio2 = ar2 / targetAspectRatio; + float distance2 = Math.abs(1 - arRatio2); + + // Reverse the order because lower distance is better + cmp = Float.compare(distance2, distance1); + if (cmp != 0) { + return cmp; + } + } + + // Greater height is better + return Integer.compare(s1.getHeight(), s2.getHeight()); + }); + + if (selected.isPresent()) { + android.util.Size size = selected.get(); + return new Size(size.getWidth(), size.getHeight()); + } + + // Not found + return null; + } + + private static Float resolveAspectRatio(CameraAspectRatio ratio, CameraCharacteristics characteristics) { + if (ratio == null) { + return null; + } + + if (ratio.isSensor()) { + Rect activeSize = characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE); + return (float) activeSize.width() / activeSize.height(); + } + + return ratio.getAspectRatio(); + } + + @TargetApi(AndroidVersions.API_30_ANDROID_11) + @Override + public void start(Surface surface) throws IOException { + if (transform != null) { + assert glRunner == null; + OpenGLFilter glFilter = new AffineOpenGLFilter(transform); + // The transform matrix returned by SurfaceTexture is incorrect for camera capture (it often contains an additional unexpected 90° + // rotation). Use a vertical flip transform matrix instead. + glRunner = new OpenGLRunner(glFilter, VFLIP_MATRIX); + surface = glRunner.start(captureSize, videoSize, surface); + } + + cameraHandler.post(() -> { + assertCameraThread(); + started = true; + }); + + Surface captureSurface = surface; + OutputConfiguration outputConfig = new OutputConfiguration(captureSurface); + List outputs = Collections.singletonList(outputConfig); + int sessionType = highSpeed ? SessionConfiguration.SESSION_HIGH_SPEED : SessionConfiguration.SESSION_REGULAR; + SessionConfiguration sessionConfig = new SessionConfiguration(sessionType, outputs, cameraExecutor, new CameraCaptureSession.StateCallback() { + @Override + public void onConfigured(CameraCaptureSession session) { + assertCameraThread(); + if (!started) { + // Stopped on the encoder thread between the call to start() and this callback + return; + } + + CameraManager cameraManager = ServiceManager.getCameraManager(); + try { + CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraId); + zoomRange = characteristics.get(CameraCharacteristics.CONTROL_ZOOM_RATIO_RANGE); + } catch (CameraAccessException e) { + Ln.w("Could not get camera characteristics"); + } + + try { + requestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD); + requestBuilder.addTarget(captureSurface); + + if (fps > 0) { + requestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, new Range<>(fps, fps)); + } + if (initialTorch) { + Ln.i("Turn camera torch on"); + requestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_TORCH); + } + if (zoom != 1) { + zoom = clampZoom(zoom); + Ln.i("Set camera zoom: " + zoom); + requestBuilder.set(CaptureRequest.CONTROL_ZOOM_RATIO, zoom); + } + + CaptureRequest request = requestBuilder.build(); + setRepeatingRequest(session, request); + currentSession = session; + } catch (CameraAccessException e) { + Ln.e("Camera error", e); + disconnected.set(true); + getCaptureControl().reset(CaptureControl.RESET_REASON_TERMINATED); + } + } + + @Override + public void onConfigureFailed(CameraCaptureSession session) { + Ln.e("Camera configuration error"); + disconnected.set(true); + getCaptureControl().reset(CaptureControl.RESET_REASON_TERMINATED); + } + }); + + try { + cameraDevice.createCaptureSession(sessionConfig); + } catch (CameraAccessException e) { + stop(); + throw new IOException(e); + } + } + + @Override + public void stop() { + cameraHandler.post(() -> { + assertCameraThread(); + currentSession = null; + requestBuilder = null; + started = false; + }); + + if (glRunner != null) { + glRunner.stopAndRelease(); + glRunner = null; + } + } + + @Override + public void release() { + if (cameraDevice != null) { + cameraDevice.close(); + } + if (cameraThread != null) { + cameraThread.quitSafely(); + } + } + + @Override + public Size getSize() { + return videoSize; + } + + @Override + protected boolean applyNewVideoConstraints(VideoConstraints videoConstraints) { + if (explicitSize != null) { + return false; + } + + this.videoConstraints = videoConstraints; + return true; + } + + @SuppressLint("MissingPermission") + @TargetApi(AndroidVersions.API_31_ANDROID_12) + private CameraDevice openCamera(String id) throws CameraAccessException, InterruptedException { + CompletableFuture future = new CompletableFuture<>(); + ServiceManager.getCameraManager().openCamera(id, new CameraDevice.StateCallback() { + @Override + public void onOpened(CameraDevice camera) { + Ln.d("Camera opened successfully"); + future.complete(camera); + } + + @Override + public void onDisconnected(CameraDevice camera) { + Ln.w("Camera disconnected"); + disconnected.set(true); + getCaptureControl().reset(CaptureControl.RESET_REASON_TERMINATED); + } + + @Override + public void onError(CameraDevice camera, int error) { + int cameraAccessExceptionErrorCode; + switch (error) { + case CameraDevice.StateCallback.ERROR_CAMERA_IN_USE: + cameraAccessExceptionErrorCode = CameraAccessException.CAMERA_IN_USE; + break; + case CameraDevice.StateCallback.ERROR_MAX_CAMERAS_IN_USE: + cameraAccessExceptionErrorCode = CameraAccessException.MAX_CAMERAS_IN_USE; + break; + case CameraDevice.StateCallback.ERROR_CAMERA_DISABLED: + cameraAccessExceptionErrorCode = CameraAccessException.CAMERA_DISABLED; + break; + case CameraDevice.StateCallback.ERROR_CAMERA_DEVICE: + case CameraDevice.StateCallback.ERROR_CAMERA_SERVICE: + default: + cameraAccessExceptionErrorCode = CameraAccessException.CAMERA_ERROR; + break; + } + future.completeExceptionally(new CameraAccessException(cameraAccessExceptionErrorCode)); + } + }, cameraHandler); + + try { + return future.get(); + } catch (ExecutionException e) { + throw (CameraAccessException) e.getCause(); + } + } + + @TargetApi(AndroidVersions.API_31_ANDROID_12) + private void setRepeatingRequest(CameraCaptureSession session, CaptureRequest request) throws CameraAccessException { + CameraCaptureSession.CaptureCallback callback = new CameraCaptureSession.CaptureCallback() { + @Override + public void onCaptureStarted(CameraCaptureSession session, CaptureRequest request, long timestamp, long frameNumber) { + // Called for each frame captured, do nothing + } + + @Override + public void onCaptureFailed(CameraCaptureSession session, CaptureRequest request, CaptureFailure failure) { + Ln.w("Camera capture failed: frame " + failure.getFrameNumber()); + } + }; + + if (highSpeed) { + CameraConstrainedHighSpeedCaptureSession highSpeedSession = (CameraConstrainedHighSpeedCaptureSession) session; + List requests = highSpeedSession.createHighSpeedRequestList(request); + highSpeedSession.setRepeatingBurst(requests, callback, cameraHandler); + } else { + session.setRepeatingRequest(request, callback, cameraHandler); + } + } + + @Override + public boolean isClosed() { + return disconnected.get(); + } + + public void setTorchEnabled(boolean enabled) { + cameraHandler.post(() -> { + assertCameraThread(); + if (currentSession != null && requestBuilder != null) { + try { + Ln.i("Turn camera torch " + (enabled ? "on" : "off")); + requestBuilder.set(CaptureRequest.FLASH_MODE, enabled ? CaptureRequest.FLASH_MODE_TORCH : CaptureRequest.FLASH_MODE_OFF); + CaptureRequest request = requestBuilder.build(); + setRepeatingRequest(currentSession, request); + } catch (CameraAccessException e) { + Ln.e("Camera error", e); + } + } + }); + } + + @TargetApi(AndroidVersions.API_30_ANDROID_11) + private void zoom(boolean in) { + cameraHandler.post(() -> { + assertCameraThread(); + if (currentSession != null && requestBuilder != null) { + // Always align to log values + double z = Math.round(Math.log(zoom) / Math.log(ZOOM_FACTOR)); + double dir = in ? 1 : -1; + zoom = (float) Math.pow(ZOOM_FACTOR, z + dir); + + try { + zoom = clampZoom(zoom); + Ln.i("Set camera zoom: " + zoom); + requestBuilder.set(CaptureRequest.CONTROL_ZOOM_RATIO, zoom); + CaptureRequest request = requestBuilder.build(); + setRepeatingRequest(currentSession, request); + } catch (CameraAccessException e) { + Ln.e("Camera error", e); + } + } + }); + } + + public void zoomIn() { + zoom(true); + } + + public void zoomOut() { + zoom(false); + } + + private float clampZoom(float value) { + assertCameraThread(); + if (zoomRange == null) { + return value; + } + + return zoomRange.clamp(value); + } + + private void assertCameraThread() { + assert Thread.currentThread() == cameraThread; + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/video/CameraFacing.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/video/CameraFacing.java new file mode 100644 index 0000000..f818e66 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/video/CameraFacing.java @@ -0,0 +1,33 @@ +package com.genymobile.scrcpy.video; + +import android.annotation.SuppressLint; +import android.hardware.camera2.CameraCharacteristics; + +public enum CameraFacing { + FRONT("front", CameraCharacteristics.LENS_FACING_FRONT), + BACK("back", CameraCharacteristics.LENS_FACING_BACK), + @SuppressLint("InlinedApi") // introduced in API 23 + EXTERNAL("external", CameraCharacteristics.LENS_FACING_EXTERNAL); + + private final String name; + private final int value; + + CameraFacing(String name, int value) { + this.name = name; + this.value = value; + } + + int value() { + return value; + } + + public static CameraFacing findByName(String name) { + for (CameraFacing facing : CameraFacing.values()) { + if (name.equals(facing.name)) { + return facing; + } + } + + return null; + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/video/CaptureControl.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/video/CaptureControl.java new file mode 100644 index 0000000..7933ea7 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/video/CaptureControl.java @@ -0,0 +1,42 @@ +package com.genymobile.scrcpy.video; + +import android.media.MediaCodec; + +public class CaptureControl { + + public static final int RESET_REASON_TERMINATED = 1; + public static final int RESET_REASON_DISPLAY_PROPERTIES_CHANGED = 1 << 1; + public static final int RESET_REASON_CLIENT_RESET = 1 << 2; + public static final int RESET_REASON_CLIENT_RESIZED = 1 << 3; + + private int reset = 0; + + // Current instance of MediaCodec to "interrupt" on reset + private MediaCodec runningMediaCodec; + + public synchronized boolean isResetRequested() { + return reset != 0; + } + + public synchronized int consumeReset() { + int value = reset; + reset = 0; + return value; + } + + public synchronized void reset(int reason) { + assert reason != 0; + reset |= reason; + if (runningMediaCodec != null) { + try { + runningMediaCodec.signalEndOfInputStream(); + } catch (IllegalStateException e) { + // ignore + } + } + } + + public synchronized void setRunningMediaCodec(MediaCodec runningMediaCodec) { + this.runningMediaCodec = runningMediaCodec; + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/video/NewDisplayCapture.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/video/NewDisplayCapture.java new file mode 100644 index 0000000..5ed4c73 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/video/NewDisplayCapture.java @@ -0,0 +1,374 @@ +package com.genymobile.scrcpy.video; + +import com.genymobile.scrcpy.AndroidVersions; +import com.genymobile.scrcpy.Options; +import com.genymobile.scrcpy.control.PositionMapper; +import com.genymobile.scrcpy.display.DisplayInfo; +import com.genymobile.scrcpy.display.DisplayMonitor; +import com.genymobile.scrcpy.display.DisplayProperties; +import com.genymobile.scrcpy.display.DisplayPropertiesTracker; +import com.genymobile.scrcpy.display.DisplayResizeDebouncer; +import com.genymobile.scrcpy.model.NewDisplay; +import com.genymobile.scrcpy.model.Orientation; +import com.genymobile.scrcpy.model.Size; +import com.genymobile.scrcpy.opengl.AffineOpenGLFilter; +import com.genymobile.scrcpy.opengl.OpenGLFilter; +import com.genymobile.scrcpy.opengl.OpenGLRunner; +import com.genymobile.scrcpy.util.AffineMatrix; +import com.genymobile.scrcpy.util.Ln; +import com.genymobile.scrcpy.wrappers.ServiceManager; + +import android.graphics.Rect; +import android.hardware.display.VirtualDisplay; +import android.os.Build; +import android.view.Surface; + +import java.io.IOException; + +public class NewDisplayCapture extends SurfaceCapture { + + // Internal fields copied from android.hardware.display.DisplayManager + private static final int VIRTUAL_DISPLAY_FLAG_PUBLIC = android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC; + private static final int VIRTUAL_DISPLAY_FLAG_PRESENTATION = android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION; + private static final int VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY = android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY; + private static final int VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH = 1 << 6; + private static final int VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT = 1 << 7; + private static final int VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL = 1 << 8; + private static final int VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS = 1 << 9; + private static final int VIRTUAL_DISPLAY_FLAG_TRUSTED = 1 << 10; + private static final int VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP = 1 << 11; + private static final int VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED = 1 << 12; + private static final int VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED = 1 << 13; + private static final int VIRTUAL_DISPLAY_FLAG_OWN_FOCUS = 1 << 14; + private static final int VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP = 1 << 15; + + private final VirtualDisplayListener vdListener; + private final NewDisplay newDisplay; + + private final DisplayMonitor displayMonitor = new DisplayMonitor(); + + private AffineMatrix displayTransform; + private AffineMatrix eventTransform; + private OpenGLRunner glRunner; + + private Size mainDisplaySize; + private int mainDisplayDpi; + private final int displayImePolicy; + private final Rect crop; + private final boolean captureOrientationLocked; + private final Orientation captureOrientation; + private final float angle; + private final boolean vdDestroyContent; + private final boolean vdSystemDecorations; + private final boolean flexDisplay; + + private VideoConstraints videoConstraints; + + private VirtualDisplay virtualDisplay; + private Size videoSize; + private Size displaySize; // the logical size of the display (including rotation) + private Size physicalSize; // the physical size of the display (without rotation) + + private DisplayPropertiesTracker tracker; + private DisplayResizeDebouncer debouncer; + + private int dpi; + + public NewDisplayCapture(VirtualDisplayListener vdListener, Options options) { + this.vdListener = vdListener; + this.newDisplay = options.getNewDisplay(); + assert newDisplay != null; + this.displayImePolicy = options.getDisplayImePolicy(); + this.crop = options.getCrop(); + assert options.getCaptureOrientationLock() != null; + this.captureOrientationLocked = options.getCaptureOrientationLock() != Orientation.Lock.Unlocked; + this.captureOrientation = options.getCaptureOrientation(); + assert captureOrientation != null; + this.angle = options.getAngle(); + this.vdDestroyContent = options.getVDDestroyContent(); + this.vdSystemDecorations = options.getVDSystemDecorations(); + this.flexDisplay = options.getFlexDisplay(); + } + + @Override + protected void init(VideoConstraints videoConstraints) { + setVideoConstraints(videoConstraints); // synchronized + + displaySize = newDisplay.getSize(); + dpi = newDisplay.getDpi(); + if (flexDisplay) { + if (crop != null) { + throw new IllegalArgumentException("Flex display does not support cropping"); + } + + tracker = new DisplayPropertiesTracker(); + debouncer = new DisplayResizeDebouncer(this::triggerResize); + debouncer.start(); + + // Hardcode default values if not defined + if (displaySize == null) { + displaySize = new Size(1280, 960); + } + if (dpi == 0) { + dpi = 160; + } + } else if (displaySize == null || dpi == 0) { + DisplayInfo displayInfo = ServiceManager.getDisplayManager().getDisplayInfo(0); + if (displayInfo != null) { + mainDisplaySize = displayInfo.getSize(); + if ((displayInfo.getRotation() % 2) != 0) { + mainDisplaySize = mainDisplaySize.rotate(); // Use the natural device orientation (at rotation 0), not the current one + } + mainDisplayDpi = displayInfo.getDpi(); + } else { + Ln.w("Main display not found, fallback to 1920x1080 240dpi"); + mainDisplaySize = new Size(1920, 1080); + mainDisplayDpi = 240; + } + } + } + + @Override + public void prepare() { + int displayRotation; + if (virtualDisplay == null) { + if (flexDisplay) { + assert displaySize != null; + displaySize = displaySize.constrain(videoConstraints, false); + } else { + if (displaySize == null) { + assert !flexDisplay; + displaySize = mainDisplaySize; + } + + // Align the physical display size to avoid unnecessary mismatches with the output size + displaySize = displaySize.align(videoConstraints.getAlignment()); + } + + if (dpi == 0) { + assert !flexDisplay; + dpi = scaleDpi(mainDisplaySize, mainDisplayDpi, displaySize); + } + + displayRotation = 0; + // Set the current display properties to avoid an unnecessary capture reset + displayMonitor.setSessionDisplayProperties(new DisplayProperties(displaySize, displayRotation)); + } else { + DisplayInfo displayInfo = ServiceManager.getDisplayManager().getDisplayInfo(virtualDisplay.getDisplay().getDisplayId()); + dpi = displayInfo.getDpi(); + displayRotation = displayInfo.getRotation(); + displaySize = displayInfo.getSize(); + if (flexDisplay) { + displaySize = displaySize.constrain(videoConstraints, false); + } else { + // Align the physical display size to avoid unnecessary mismatches with the output size + displaySize = displaySize.align(videoConstraints.getAlignment()); + } + } + + VideoFilter filter = new VideoFilter(displaySize); + + if (crop != null) { + boolean transposed = (displayRotation % 2) != 0; + filter.addCrop(crop, transposed); + } + + filter.addOrientation(displayRotation, captureOrientationLocked, captureOrientation); + filter.addAngle(angle); + + if (!flexDisplay) { + Size outputSize = filter.getOutputSize(); + Size filteredSize = outputSize.constrain(videoConstraints); + if (!filteredSize.equals(outputSize)) { + filter.addResize(filteredSize); + } + } + + eventTransform = filter.getInverseTransform(); + + // DisplayInfo gives the oriented size (so videoSize includes the display rotation) + videoSize = filter.getOutputSize(); + + // However, the virtual display video always remains in its original orientation, so it must be rotated manually. + // This additional display rotation must not be included in the input events transform (the expected coordinates are already in the + // physical display size) + if ((displayRotation % 2) == 0) { + physicalSize = displaySize; + } else { + physicalSize = displaySize.rotate(); + } + VideoFilter displayFilter = new VideoFilter(physicalSize); + displayFilter.addRotation(displayRotation); + AffineMatrix displayRotationMatrix = displayFilter.getInverseTransform(); + + // Take care of multiplication order: + // displayTransform = (FILTER_MATRIX * DISPLAY_FILTER_MATRIX)⁻¹ + // = DISPLAY_FILTER_MATRIX⁻¹ * FILTER_MATRIX⁻¹ + // = displayRotationMatrix * eventTransform + displayTransform = AffineMatrix.multiplyAll(displayRotationMatrix, eventTransform); + } + + public void startNew(Surface surface) { + try { + int flags = VIRTUAL_DISPLAY_FLAG_PUBLIC + | VIRTUAL_DISPLAY_FLAG_PRESENTATION + | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY + | VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH + | VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT; + if (vdDestroyContent) { + flags |= VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL; + } + if (vdSystemDecorations) { + flags |= VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS; + } + if (Build.VERSION.SDK_INT >= AndroidVersions.API_33_ANDROID_13) { + flags |= VIRTUAL_DISPLAY_FLAG_TRUSTED + | VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP + | VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED + | VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED; + if (Build.VERSION.SDK_INT >= AndroidVersions.API_34_ANDROID_14) { + flags |= VIRTUAL_DISPLAY_FLAG_OWN_FOCUS + | VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP; + } + } + VirtualDisplay vd = ServiceManager.getDisplayManager() + .createNewVirtualDisplay("scrcpy", displaySize.getWidth(), displaySize.getHeight(), dpi, surface, flags); + setCurrentVirtualDisplay(vd); // used for client resize + int virtualDisplayId = vd.getDisplay().getDisplayId(); + Ln.i("New display: " + displaySize.getWidth() + "x" + displaySize.getHeight() + "/" + dpi + " (id=" + virtualDisplayId + ")"); + + if (displayImePolicy != -1) { + ServiceManager.getWindowManager().setDisplayImePolicy(virtualDisplayId, displayImePolicy); + } + + displayMonitor.start(virtualDisplayId, (props) -> { + int reason; + if (flexDisplay) { + boolean isClientResize = tracker.onChanged(props); + if (isClientResize) { + reason = CaptureControl.RESET_REASON_CLIENT_RESIZED; + } else { + reason = CaptureControl.RESET_REASON_DISPLAY_PROPERTIES_CHANGED; + // Display properties have changed, cancel pending client resize requests + debouncer.cancelResize(); + } + } else { + reason = CaptureControl.RESET_REASON_DISPLAY_PROPERTIES_CHANGED; + } + getCaptureControl().reset(reason); + }); + } catch (Exception e) { + Ln.e("Could not create display", e); + throw new AssertionError("Could not create display"); + } + } + + @Override + public void start(Surface surface) throws IOException { + if (displayTransform != null) { + assert glRunner == null; + OpenGLFilter glFilter = new AffineOpenGLFilter(displayTransform); + glRunner = new OpenGLRunner(glFilter); + surface = glRunner.start(physicalSize, videoSize, surface); + } + + if (virtualDisplay == null) { + startNew(surface); + } else { + virtualDisplay.setSurface(surface); + } + + if (vdListener != null) { + PositionMapper positionMapper = PositionMapper.create(videoSize, eventTransform, displaySize); + vdListener.onNewVirtualDisplay(virtualDisplay.getDisplay().getDisplayId(), positionMapper); + } + } + + @Override + public void stop() { + if (glRunner != null) { + glRunner.stopAndRelease(); + glRunner = null; + } + } + + @Override + public void release() { + displayMonitor.stopAndRelease(); + + if (debouncer != null) { + debouncer.stop(); + } + + if (virtualDisplay != null) { + // synchronized with triggerResize() + synchronized (this) { + virtualDisplay.release(); + setCurrentVirtualDisplay(null); + } + } + } + + @Override + public Size getSize() { + return videoSize; + } + + @Override + protected boolean applyNewVideoConstraints(VideoConstraints videoConstraints) { + setVideoConstraints(videoConstraints); // with synchronization + return true; + } + + private static int scaleDpi(Size initialSize, int initialDpi, Size size) { + int den = initialSize.getMax(); + int num = size.getMax(); + return initialDpi * num / den; + } + + public void requestResize(int width, int height) { + if (!flexDisplay) { + throw new IllegalStateException("Cannot resize a non-flex display"); + } + + VideoConstraints constraints = getVideoConstraints(); // synchronized + Size newSize = new Size(width, height).constrain(constraints, false); + if (Ln.isEnabled(Ln.Level.VERBOSE)) { + Ln.v(getClass().getSimpleName() + ": requestResize(" + width + ", " + height + ")"); + Ln.v(getClass().getSimpleName() + ": constrained size = " + newSize); + } + + debouncer.requestResize(newSize); + } + + private synchronized void setCurrentVirtualDisplay(VirtualDisplay virtualDisplay) { + this.virtualDisplay = virtualDisplay; + } + + private synchronized VideoConstraints getVideoConstraints() { + return videoConstraints; + } + + private synchronized void setVideoConstraints(VideoConstraints videoConstraints) { + this.videoConstraints = videoConstraints; + } + + private synchronized void triggerResize(Size size) { + if (virtualDisplay != null) { + size = size.constrain(videoConstraints, false); // in case the constraints have changed + int displayId = virtualDisplay.getDisplay().getDisplayId(); + DisplayInfo displayInfo = ServiceManager.getDisplayManager().getDisplayInfo(displayId); + @SuppressWarnings("checkstyle:HiddenField") // hides this.dpi on purpose + int dpi = displayInfo.getDpi(); + int displayRotation = displayInfo.getRotation(); + if (captureOrientation.isSwap()) { + size = size.rotate(); + } + tracker.pushClientRequest(new DisplayProperties(size, displayRotation)); + + // Although the display size (as detected by the DisplayMonitor) is rotated, the virtual display itself is not + Size vdSize = (displayRotation % 2) == 0 ? size : size.rotate(); + virtualDisplay.resize(vdSize.getWidth(), vdSize.getHeight(), dpi); + } + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/video/ScreenCapture.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/video/ScreenCapture.java new file mode 100644 index 0000000..0768181 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/video/ScreenCapture.java @@ -0,0 +1,228 @@ +package com.genymobile.scrcpy.video; + +import com.genymobile.scrcpy.AndroidVersions; +import com.genymobile.scrcpy.Options; +import com.genymobile.scrcpy.control.PositionMapper; +import com.genymobile.scrcpy.device.Device; +import com.genymobile.scrcpy.display.DisplayInfo; +import com.genymobile.scrcpy.display.DisplayMonitor; +import com.genymobile.scrcpy.display.DisplayProperties; +import com.genymobile.scrcpy.model.ConfigurationException; +import com.genymobile.scrcpy.model.Orientation; +import com.genymobile.scrcpy.model.Size; +import com.genymobile.scrcpy.opengl.AffineOpenGLFilter; +import com.genymobile.scrcpy.opengl.OpenGLFilter; +import com.genymobile.scrcpy.opengl.OpenGLRunner; +import com.genymobile.scrcpy.util.AffineMatrix; +import com.genymobile.scrcpy.util.Ln; +import com.genymobile.scrcpy.util.LogUtils; +import com.genymobile.scrcpy.wrappers.ServiceManager; +import com.genymobile.scrcpy.wrappers.SurfaceControl; + +import android.graphics.Rect; +import android.hardware.display.VirtualDisplay; +import android.os.Build; +import android.os.IBinder; +import android.view.Surface; + +import java.io.IOException; +import java.util.Locale; + +public class ScreenCapture extends SurfaceCapture { + + private final VirtualDisplayListener vdListener; + private final int displayId; + private final Rect crop; + private Orientation.Lock captureOrientationLock; + private Orientation captureOrientation; + private final float angle; + + private VideoConstraints videoConstraints; + + private DisplayInfo displayInfo; + private Size videoSize; + + private final DisplayMonitor displayMonitor = new DisplayMonitor(); + + private IBinder display; + private VirtualDisplay virtualDisplay; + + private AffineMatrix transform; + private OpenGLRunner glRunner; + + public ScreenCapture(VirtualDisplayListener vdListener, Options options) { + this.vdListener = vdListener; + this.displayId = options.getDisplayId(); + assert displayId != Device.DISPLAY_ID_NONE; + this.crop = options.getCrop(); + this.captureOrientationLock = options.getCaptureOrientationLock(); + this.captureOrientation = options.getCaptureOrientation(); + assert captureOrientationLock != null; + assert captureOrientation != null; + this.angle = options.getAngle(); + } + + @Override + public void init(VideoConstraints videoConstraints) { + this.videoConstraints = videoConstraints; + displayMonitor.start(displayId, (props) -> getCaptureControl().reset(CaptureControl.RESET_REASON_DISPLAY_PROPERTIES_CHANGED)); + } + + @Override + public void prepare() throws ConfigurationException { + displayInfo = ServiceManager.getDisplayManager().getDisplayInfo(displayId); + if (displayInfo == null) { + Ln.e("Display " + displayId + " not found\n" + LogUtils.buildDisplayListMessage()); + throw new ConfigurationException("Unknown display id: " + displayId); + } + + if ((displayInfo.getFlags() & DisplayInfo.FLAG_SUPPORTS_PROTECTED_BUFFERS) == 0) { + Ln.w("Display doesn't have FLAG_SUPPORTS_PROTECTED_BUFFERS flag, mirroring can be restricted"); + } + + Size displaySize = displayInfo.getSize(); + int displayRotation = displayInfo.getRotation(); + displayMonitor.setSessionDisplayProperties(new DisplayProperties(displaySize, displayRotation)); + + if (captureOrientationLock == Orientation.Lock.LockedInitial) { + // The user requested to lock the video orientation to the current orientation + captureOrientationLock = Orientation.Lock.LockedValue; + captureOrientation = Orientation.fromRotation(displayRotation); + } + + VideoFilter filter = new VideoFilter(displaySize); + + if (crop != null) { + boolean transposed = (displayRotation % 2) != 0; + filter.addCrop(crop, transposed); + } + + boolean locked = captureOrientationLock != Orientation.Lock.Unlocked; + filter.addOrientation(displayRotation, locked, captureOrientation); + filter.addAngle(angle); + + transform = filter.getInverseTransform(); + videoSize = filter.getOutputSize().constrain(videoConstraints); + } + + @Override + public void start(Surface surface) throws IOException { + if (display != null) { + SurfaceControl.destroyDisplay(display); + display = null; + } + if (virtualDisplay != null) { + virtualDisplay.release(); + virtualDisplay = null; + } + + Size inputSize; + if (transform != null) { + // If there is a filter, it must receive the full display content + inputSize = displayInfo.getSize(); + assert glRunner == null; + OpenGLFilter glFilter = new AffineOpenGLFilter(transform); + glRunner = new OpenGLRunner(glFilter); + surface = glRunner.start(inputSize, videoSize, surface); + } else { + // If there is no filter, the display must be rendered at target video size directly + inputSize = videoSize; + } + + try { + virtualDisplay = ServiceManager.getDisplayManager() + .createVirtualDisplay("scrcpy", inputSize.getWidth(), inputSize.getHeight(), displayId, surface); + Ln.d("Display: using DisplayManager API"); + } catch (Exception displayManagerException) { + if (Build.BRAND.equalsIgnoreCase("oculus") && Build.MODEL.toLowerCase(Locale.ROOT).startsWith("quest")) { + // Workaround for buggy createVirtualDisplay on Quest + try { + virtualDisplay = (VirtualDisplay) VirtualDisplay.class.getDeclaredConstructors()[0].newInstance(null, null, null, surface); + } catch (ReflectiveOperationException e) { + Ln.e("Could not create VirtualDisplay", e); + } + } else { + try { + display = createDisplay(); + + Size deviceSize = displayInfo.getSize(); + int layerStack = displayInfo.getLayerStack(); + setDisplaySurface(display, surface, deviceSize.toRect(), inputSize.toRect(), layerStack); + Ln.d("Display: using SurfaceControl API"); + } catch (Exception surfaceControlException) { + Ln.e("Could not create display using DisplayManager", displayManagerException); + Ln.e("Could not create display using SurfaceControl", surfaceControlException); + throw new AssertionError("Could not create display"); + } + } + } + + if (vdListener != null) { + int virtualDisplayId; + PositionMapper positionMapper; + if (virtualDisplay == null || displayId == 0) { + // Surface control or main display: send all events to the original display, relative to the device size + Size deviceSize = displayInfo.getSize(); + positionMapper = PositionMapper.create(videoSize, transform, deviceSize); + virtualDisplayId = displayId; + } else { + // The positions are relative to the virtual display, not the original display (so use inputSize, not deviceSize!) + positionMapper = PositionMapper.create(videoSize, transform, inputSize); + virtualDisplayId = virtualDisplay.getDisplay().getDisplayId(); + } + vdListener.onNewVirtualDisplay(virtualDisplayId, positionMapper); + } + } + + @Override + public void stop() { + if (glRunner != null) { + glRunner.stopAndRelease(); + glRunner = null; + } + } + + @Override + public void release() { + displayMonitor.stopAndRelease(); + + if (display != null) { + SurfaceControl.destroyDisplay(display); + display = null; + } + if (virtualDisplay != null) { + virtualDisplay.release(); + virtualDisplay = null; + } + } + + @Override + public Size getSize() { + return videoSize; + } + + @Override + protected boolean applyNewVideoConstraints(VideoConstraints videoConstraints) { + this.videoConstraints = videoConstraints; + return true; + } + + private static IBinder createDisplay() throws Exception { + // Since Android 12 (preview), secure displays could not be created with shell permissions anymore. + // On Android 12 preview, SDK_INT is still R (not S), but CODENAME is "S". + boolean secure = Build.VERSION.SDK_INT < AndroidVersions.API_30_ANDROID_11 || (Build.VERSION.SDK_INT == AndroidVersions.API_30_ANDROID_11 + && !"S".equals(Build.VERSION.CODENAME)); + return SurfaceControl.createDisplay("scrcpy", secure); + } + + private static void setDisplaySurface(IBinder display, Surface surface, Rect deviceRect, Rect displayRect, int layerStack) { + SurfaceControl.openTransaction(); + try { + SurfaceControl.setDisplaySurface(display, surface); + SurfaceControl.setDisplayProjection(display, 0, deviceRect, displayRect); + SurfaceControl.setDisplayLayerStack(display, layerStack); + } finally { + SurfaceControl.closeTransaction(); + } + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/video/SurfaceCapture.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/video/SurfaceCapture.java new file mode 100644 index 0000000..4dd2879 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/video/SurfaceCapture.java @@ -0,0 +1,85 @@ +package com.genymobile.scrcpy.video; + +import com.genymobile.scrcpy.model.ConfigurationException; +import com.genymobile.scrcpy.model.Size; + +import android.view.Surface; + +import java.io.IOException; + +/** + * A video source which can be rendered on a Surface for encoding. + */ +public abstract class SurfaceCapture { + + private CaptureControl captureControl; + + /** + * Called once before the first capture starts. + */ + public final void init(CaptureControl captureControl, VideoConstraints videoConstraints) throws ConfigurationException, IOException { + this.captureControl = captureControl; + init(videoConstraints); + } + + public CaptureControl getCaptureControl() { + return captureControl; + } + + /** + * Called once before the first capture starts. + * + * @param videoConstraints the video constraints + */ + protected abstract void init(VideoConstraints videoConstraints) throws ConfigurationException, IOException; + + /** + * Called after the last capture ends (if and only if {@link #init(VideoConstraints)} has been called). + */ + public abstract void release(); + + /** + * Called once before each capture starts, before {@link #getSize()}. + */ + public void prepare() throws ConfigurationException, IOException { + // empty by default + } + + /** + * Start the capture to the target surface. + * + * @param surface the surface which will be encoded + */ + public abstract void start(Surface surface) throws IOException; + + /** + * Stop the capture. + */ + public void stop() { + // Do nothing by default + } + + /** + * Return the video size + * + * @return the video size + */ + public abstract Size getSize(); + + /** + * Indicate if the capture has been closed internally. + * + * @return {@code true} is the capture is closed, {@code false} otherwise. + */ + public boolean isClosed() { + return false; + } + + /** + * Set new video constraints (called by the encoder to retry when encoding fails) + * + * @param videoConstraints the video constraints + * @return {@code true} if the implementation accepts the new constraints, {@code false} otherwise. + */ + protected abstract boolean applyNewVideoConstraints(VideoConstraints videoConstraints); +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/video/SurfaceEncoder.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/video/SurfaceEncoder.java new file mode 100644 index 0000000..539681b --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/video/SurfaceEncoder.java @@ -0,0 +1,388 @@ +package com.genymobile.scrcpy.video; + +import com.genymobile.scrcpy.AndroidVersions; +import com.genymobile.scrcpy.AsyncProcessor; +import com.genymobile.scrcpy.Options; +import com.genymobile.scrcpy.device.Streamer; +import com.genymobile.scrcpy.model.Codec; +import com.genymobile.scrcpy.model.CodecOption; +import com.genymobile.scrcpy.model.ConfigurationException; +import com.genymobile.scrcpy.model.Size; +import com.genymobile.scrcpy.util.CodecUtils; +import com.genymobile.scrcpy.util.IO; +import com.genymobile.scrcpy.util.Ln; +import com.genymobile.scrcpy.util.LogUtils; + +import android.media.MediaCodec; +import android.media.MediaCodecInfo; +import android.media.MediaFormat; +import android.os.Build; +import android.os.Looper; +import android.os.SystemClock; +import android.view.Surface; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +public class SurfaceEncoder implements AsyncProcessor { + + private static final int DEFAULT_I_FRAME_INTERVAL = 10; // seconds + private static final int REPEAT_FRAME_DELAY_US = 100_000; // repeat after 100ms + private static final String KEY_MAX_FPS_TO_ENCODER = "max-fps-to-encoder"; + + // Keep the values in descending order + private static final int[] MAX_SIZE_FALLBACK = {2560, 1920, 1600, 1280, 1024, 800}; + private static final int MAX_CONSECUTIVE_ERRORS = 3; + + private final SurfaceCapture capture; + private final Streamer streamer; + private final String encoderName; + private final List codecOptions; + private final int videoBitRate; + private final int maxSize; + private final float maxFps; + private final boolean downsizeOnError; + private final int minSizeAlignment; + private final boolean ignoreVideoEncoderConstraints; + + private boolean firstFrameSent; + private int consecutiveErrors; + + private Thread thread; + private final AtomicBoolean stopped = new AtomicBoolean(); + + private final CaptureControl captureControl = new CaptureControl(); + + private VideoConstraints videoConstraints; + + public SurfaceEncoder(SurfaceCapture capture, Streamer streamer, Options options) { + this.capture = capture; + this.streamer = streamer; + this.videoBitRate = options.getVideoBitRate(); + this.maxSize = options.getMaxSize(); + this.maxFps = options.getMaxFps(); + this.codecOptions = options.getVideoCodecOptions(); + this.encoderName = options.getVideoEncoder(); + this.downsizeOnError = options.getDownsizeOnError(); + this.minSizeAlignment = options.getMinSizeAlignment(); + this.ignoreVideoEncoderConstraints = options.getIgnoreVideoEncoderConstraints(); + } + + private void streamCapture() throws IOException, ConfigurationException { + Codec codec = streamer.getCodec(); + MediaCodec mediaCodec = createMediaCodec(codec, encoderName); + MediaFormat format = createFormat(codec.getMimeType(), videoBitRate, maxFps, codecOptions); + + MediaCodecInfo.VideoCapabilities caps; + int alignment; + if (ignoreVideoEncoderConstraints) { + caps = null; + alignment = 1; + } else { + caps = mediaCodec.getCodecInfo().getCapabilitiesForType(codec.getMimeType()).getVideoCapabilities(); + assert caps != null; // caps cannot be null for a video codec + alignment = Math.max(caps.getWidthAlignment(), caps.getHeightAlignment()); + Ln.d("Video codec size alignment requirement: " + alignment + "px"); + } + if (alignment < minSizeAlignment) { + alignment = minSizeAlignment; + Ln.d("Actual video size alignment: " + alignment + "px"); + } + + // Do not constrain by the declared video encoder capabilities before encoding actually fails + videoConstraints = new VideoConstraints(maxSize, alignment, null); + + capture.init(captureControl, videoConstraints); + + try { + boolean alive; + + streamer.writeVideoHeader(); + + int retainedResetReasons = 0; + + do { + int resetReasons = captureControl.consumeReset(); + if ((resetReasons & CaptureControl.RESET_REASON_TERMINATED) != 0) { + break; + } + if (retainedResetReasons != 0) { + // The reasons for the previous failed encoding must be preserved when retrying + resetReasons |= retainedResetReasons; + retainedResetReasons = 0; + } + + capture.prepare(); + Size size = capture.getSize(); + + format.setInteger(MediaFormat.KEY_WIDTH, size.getWidth()); + format.setInteger(MediaFormat.KEY_HEIGHT, size.getHeight()); + + Surface surface = null; + boolean mediaCodecStarted = false; + boolean captureStarted = false; + try { + mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); + surface = mediaCodec.createInputSurface(); + + capture.start(surface); + captureStarted = true; + + mediaCodec.start(); + mediaCodecStarted = true; + + // Set the MediaCodec instance to "interrupt" (by signaling an EOS) on reset + captureControl.setRunningMediaCodec(mediaCodec); + + if (stopped.get()) { + alive = false; + } else { + if (!captureControl.isResetRequested()) { + // The reset is due to a resize initiated by the client + boolean isClientResize = (resetReasons & CaptureControl.RESET_REASON_CLIENT_RESIZED) != 0 + && (resetReasons & CaptureControl.RESET_REASON_DISPLAY_PROPERTIES_CHANGED) == 0; + streamer.writeSessionMeta(size.getWidth(), size.getHeight(), isClientResize); + + // If a reset is requested during encode(), it will interrupt the encoding by an EOS + encode(mediaCodec, streamer); + } + + // The capture might have been closed internally (for example if the camera is disconnected) + alive = !stopped.get() && !capture.isClosed(); + } + } catch (IllegalStateException | IllegalArgumentException | IOException e) { + if (IO.isBrokenPipe(e)) { + // Do not retry on broken pipe, which is expected on close because the socket is closed by the client + throw e; + } + Ln.e("Capture/encoding error: " + e.getClass().getName() + ": " + e.getMessage()); + if (!prepareRetry(caps, size)) { + throw e; + } + // Keep the current resetReasons flags for the retry + retainedResetReasons = resetReasons; + alive = true; + } finally { + captureControl.setRunningMediaCodec(null); + if (captureStarted) { + capture.stop(); + } + if (mediaCodecStarted) { + try { + mediaCodec.stop(); + } catch (IllegalStateException e) { + // ignore (just in case) + } + } + mediaCodec.reset(); + if (surface != null) { + surface.release(); + } + } + } while (alive); + } finally { + mediaCodec.release(); + capture.release(); + } + } + + private boolean prepareRetry(MediaCodecInfo.VideoCapabilities caps, Size currentSize) { + if (firstFrameSent) { + ++consecutiveErrors; + if (consecutiveErrors < MAX_CONSECUTIVE_ERRORS) { + // Wait a bit to increase the probability that retrying will fix the problem + SystemClock.sleep(50); + return true; + } + } + + if (!downsizeOnError) { + // Must fail immediately + return false; + } + + if (caps != null && videoConstraints.getEncoderCapabilities() == null) { + assert !ignoreVideoEncoderConstraints : "caps != null implies !ignoreVideoEncoderConstraints"; + Ln.i("Applying video encoder constraints"); + videoConstraints = videoConstraints.withCapabilities(caps); + boolean accepted = capture.applyNewVideoConstraints(videoConstraints); + if (accepted) { + return true; + } + } + + if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) { + // Definitively fail + return false; + } + + // Downsizing on error is only enabled if an encoding failure occurs before the first frame, or if the video constraints were not applied + // (downsizing later could be surprising) + + int newMaxSize = chooseMaxSizeFallback(currentSize); + if (newMaxSize == 0) { + // Must definitively fail + return false; + } + + boolean accepted = capture.applyNewVideoConstraints(videoConstraints.withMaxSize(newMaxSize)); + if (!accepted) { + return false; + } + + // Retry with a smaller size + Ln.i("Retrying with -m" + newMaxSize + "..."); + return true; + } + + private static int chooseMaxSizeFallback(Size failedSize) { + int currentMaxSize = Math.max(failedSize.getWidth(), failedSize.getHeight()); + for (int value : MAX_SIZE_FALLBACK) { + if (value < currentMaxSize) { + // We found a smaller value to reduce the video size + return value; + } + } + // No fallback, fail definitively + return 0; + } + + private void encode(MediaCodec codec, Streamer streamer) throws IOException { + MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); + + boolean eos; + do { + int outputBufferId = codec.dequeueOutputBuffer(bufferInfo, -1); + try { + eos = (bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0; + // On EOS, there might be data or not, depending on bufferInfo.size + if (outputBufferId >= 0 && bufferInfo.size > 0) { + boolean isConfig = (bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0; + if (!isConfig) { + // If this is not a config packet, then it contains a frame + firstFrameSent = true; + consecutiveErrors = 0; + } + + ByteBuffer codecBuffer = codec.getOutputBuffer(outputBufferId); + streamer.writePacket(codecBuffer, bufferInfo); + } + } finally { + if (outputBufferId >= 0) { + codec.releaseOutputBuffer(outputBufferId, false); + } + } + } while (!eos); + } + + private static MediaCodec createMediaCodec(Codec codec, String encoderName) throws IOException, ConfigurationException { + if (encoderName != null) { + Ln.d("Creating encoder by name: '" + encoderName + "'"); + try { + MediaCodec mediaCodec = MediaCodec.createByCodecName(encoderName); + String mimeType = Codec.getMimeType(mediaCodec); + if (!codec.getMimeType().equals(mimeType)) { + Ln.e("Video encoder type for \"" + encoderName + "\" (" + mimeType + ") does not match codec type (" + codec.getMimeType() + ")"); + throw new ConfigurationException("Incorrect encoder type: " + encoderName); + } + return mediaCodec; + } catch (IllegalArgumentException e) { + Ln.e("Video encoder '" + encoderName + "' for " + codec.getName() + " not found\n" + LogUtils.buildVideoEncoderListMessage()); + throw new ConfigurationException("Unknown encoder: " + encoderName); + } catch (IOException e) { + Ln.e("Could not create video encoder '" + encoderName + "' for " + codec.getName() + "\n" + LogUtils.buildVideoEncoderListMessage()); + throw e; + } + } + + try { + MediaCodec mediaCodec = MediaCodec.createEncoderByType(codec.getMimeType()); + Ln.d("Using video encoder: '" + mediaCodec.getName() + "'"); + return mediaCodec; + } catch (IOException | IllegalArgumentException e) { + Ln.e("Could not create default video encoder for " + codec.getName() + "\n" + LogUtils.buildVideoEncoderListMessage()); + throw e; + } + } + + private static MediaFormat createFormat(String videoMimeType, int bitRate, float maxFps, List codecOptions) { + MediaFormat format = new MediaFormat(); + format.setString(MediaFormat.KEY_MIME, videoMimeType); + format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate); + // must be present to configure the encoder, but does not impact the actual frame rate, which is variable + format.setInteger(MediaFormat.KEY_FRAME_RATE, 60); + format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); + if (Build.VERSION.SDK_INT >= AndroidVersions.API_24_ANDROID_7_0) { + format.setInteger(MediaFormat.KEY_COLOR_RANGE, MediaFormat.COLOR_RANGE_LIMITED); + } + format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, DEFAULT_I_FRAME_INTERVAL); + // display the very first frame, and recover from bad quality when no new frames + format.setLong(MediaFormat.KEY_REPEAT_PREVIOUS_FRAME_AFTER, REPEAT_FRAME_DELAY_US); // µs + if (Build.VERSION.SDK_INT >= AndroidVersions.API_23_ANDROID_6_0) { + // real-time priority + format.setInteger(MediaFormat.KEY_PRIORITY, 0); + } + if (Build.VERSION.SDK_INT >= AndroidVersions.API_26_ANDROID_8_0) { + // output 1 frame as soon as 1 frame is queued + format.setInteger(MediaFormat.KEY_LATENCY, 1); + } + if (maxFps > 0) { + // The key existed privately before Android 10: + // + // + format.setFloat(KEY_MAX_FPS_TO_ENCODER, maxFps); + } + + if (codecOptions != null) { + for (CodecOption option : codecOptions) { + String key = option.getKey(); + Object value = option.getValue(); + CodecUtils.setCodecOption(format, key, value); + Ln.d("Video codec option set: " + key + " (" + value.getClass().getSimpleName() + ") = " + value); + } + } + + return format; + } + + @Override + public void start(TerminationListener listener) { + thread = new Thread(() -> { + // Some devices (Meizu) deadlock if the video encoding thread has no Looper + // + Looper.prepare(); + + try { + streamCapture(); + } catch (ConfigurationException e) { + // Do not print stack trace, a user-friendly error-message has already been logged + } catch (IOException e) { + // Broken pipe is expected on close, because the socket is closed by the client + if (!IO.isBrokenPipe(e)) { + Ln.e("Video encoding error", e); + } + } finally { + Ln.d("Screen streaming stopped"); + listener.onTerminated(true); + } + }, "video"); + thread.start(); + } + + @Override + public void stop() { + if (thread != null) { + stopped.set(true); + captureControl.reset(CaptureControl.RESET_REASON_TERMINATED); + } + } + + @Override + public void join() throws InterruptedException { + if (thread != null) { + thread.join(); + } + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/video/VideoCodec.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/video/VideoCodec.java new file mode 100644 index 0000000..8b9de0f --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/video/VideoCodec.java @@ -0,0 +1,54 @@ +package com.genymobile.scrcpy.video; + +import com.genymobile.scrcpy.model.Codec; + +import android.annotation.SuppressLint; +import android.media.MediaFormat; + +public enum VideoCodec implements Codec { + H264(0x68_32_36_34, "h264", MediaFormat.MIMETYPE_VIDEO_AVC), + H265(0x68_32_36_35, "h265", MediaFormat.MIMETYPE_VIDEO_HEVC), + @SuppressLint("InlinedApi") // introduced in API 29 + AV1(0x00_61_76_31, "av1", MediaFormat.MIMETYPE_VIDEO_AV1), + VP8(0x00_76_70_38, "vp8", MediaFormat.MIMETYPE_VIDEO_VP8), + VP9(0x00_76_70_39, "vp9", MediaFormat.MIMETYPE_VIDEO_VP9); + + private final int id; // 4-byte ASCII representation of the name + private final String name; + private final String mimeType; + + VideoCodec(int id, String name, String mimeType) { + this.id = id; + this.name = name; + this.mimeType = mimeType; + } + + @Override + public Type getType() { + return Type.VIDEO; + } + + @Override + public int getId() { + return id; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getMimeType() { + return mimeType; + } + + public static VideoCodec findByName(String name) { + for (VideoCodec codec : values()) { + if (codec.name.equals(name)) { + return codec; + } + } + return null; + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/video/VideoConstraints.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/video/VideoConstraints.java new file mode 100644 index 0000000..fe08631 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/video/VideoConstraints.java @@ -0,0 +1,69 @@ +package com.genymobile.scrcpy.video; + +import android.media.MediaCodecInfo; + +public class VideoConstraints { + private final int maxSize; + private final int alignment; + private final MediaCodecInfo.VideoCapabilities caps; + + public VideoConstraints(int maxSize, int alignment, MediaCodecInfo.VideoCapabilities caps) { + assert maxSize >= 0 : "Max size must not be negative"; + this.maxSize = maxSize; + + assert alignment > 0 : "Alignment must be positive"; + assert (alignment & (alignment - 1)) == 0 : "Alignment must be a power-of-two"; + this.alignment = alignment; + + this.caps = caps; + } + + /** + * Return the max size (0 if not requested) + * + * @return the max requested size + */ + public int getMaxSize() { + return maxSize; + } + + /** + * Return the video alignment. + *

+ * This a power-of-2 value that the video width and height must be multiples of. + * + * @return the video alignment + */ + public int getAlignment() { + return alignment; + } + + /** + * Return the video encoder capabilities. + * + * @return the video encoder capabilities + */ + public MediaCodecInfo.VideoCapabilities getEncoderCapabilities() { + return caps; + } + + /** + * Return the video constraints with the provided max size. + * + * @param maxSize the max requested size + * @return the new video constraints + */ + public VideoConstraints withMaxSize(int maxSize) { + return new VideoConstraints(maxSize, alignment, caps); + } + + /** + * Return the video constraints with the provided video capabilities. + * + * @param caps the video encoder capabilities + * @return the new video constraints + */ + public VideoConstraints withCapabilities(MediaCodecInfo.VideoCapabilities caps) { + return new VideoConstraints(maxSize, alignment, caps); + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/video/VideoFilter.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/video/VideoFilter.java new file mode 100644 index 0000000..9a0dec6 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/video/VideoFilter.java @@ -0,0 +1,119 @@ +package com.genymobile.scrcpy.video; + +import com.genymobile.scrcpy.model.Orientation; +import com.genymobile.scrcpy.model.Size; +import com.genymobile.scrcpy.util.AffineMatrix; + +import android.graphics.Rect; + +public class VideoFilter { + + private Size size; + private AffineMatrix transform; + + public VideoFilter(Size inputSize) { + this.size = inputSize; + } + + public Size getOutputSize() { + return size; + } + + public AffineMatrix getTransform() { + return transform; + } + + /** + * Return the inverse transform. + *

+ * The direct affine transform describes how the input image is transformed. + *

+ * It is often useful to retrieve the inverse transform instead: + *

    + *
  • The OpenGL filter expects the matrix to transform the image coordinates, which is the inverse transform;
  • + *
  • The click positions must be transformed back to the device positions, using the inverse transform too.
  • + *
+ * + * @return the inverse transform + */ + public AffineMatrix getInverseTransform() { + if (transform == null) { + return null; + } + return transform.invert(); + } + + private static Rect transposeRect(Rect rect) { + return new Rect(rect.top, rect.left, rect.bottom, rect.right); + } + + public void addCrop(Rect crop, boolean transposed) { + if (transposed) { + crop = transposeRect(crop); + } + + double inputWidth = size.getWidth(); + double inputHeight = size.getHeight(); + + if (crop.left < 0 || crop.top < 0 || crop.right > inputWidth || crop.bottom > inputHeight) { + throw new IllegalArgumentException("Crop " + crop + " exceeds the input area (" + size + ")"); + } + + double x = crop.left / inputWidth; + double y = 1 - (crop.bottom / inputHeight); // OpenGL origin is bottom-left + double w = crop.width() / inputWidth; + double h = crop.height() / inputHeight; + + transform = AffineMatrix.reframe(x, y, w, h).multiply(transform); + size = new Size(crop.width(), crop.height()); + } + + public void addRotation(int ccwRotation) { + if (ccwRotation == 0) { + return; + } + + transform = AffineMatrix.rotateOrtho(ccwRotation).multiply(transform); + if (ccwRotation % 2 != 0) { + size = size.rotate(); + } + } + + public void addOrientation(Orientation captureOrientation) { + if (captureOrientation.isFlipped()) { + transform = AffineMatrix.hflip().multiply(transform); + } + int ccwRotation = (4 - captureOrientation.getRotation()) % 4; + addRotation(ccwRotation); + } + + public void addOrientation(int displayRotation, boolean locked, Orientation captureOrientation) { + if (locked) { + // flip/rotate the current display from the natural device orientation (i.e. where display rotation is 0) + int reverseDisplayRotation = (4 - displayRotation) % 4; + addRotation(reverseDisplayRotation); + } + addOrientation(captureOrientation); + } + + public void addAngle(double cwAngle) { + if (cwAngle == 0) { + return; + } + double ccwAngle = -cwAngle; + transform = AffineMatrix.rotate(ccwAngle).withAspectRatio(size).fromCenter().multiply(transform); + } + + public void addResize(Size targetSize) { + if (size.equals(targetSize)) { + return; + } + + if (transform == null) { + // The requested scaling is performed by the viewport (by changing the output size), but the OpenGL filter must still run, even if + // resizing is not performed by the shader. So transform MUST NOT be null. + transform = AffineMatrix.IDENTITY; + } + size = targetSize; + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/video/VideoSource.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/video/VideoSource.java new file mode 100644 index 0000000..53b54a5 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/video/VideoSource.java @@ -0,0 +1,22 @@ +package com.genymobile.scrcpy.video; + +public enum VideoSource { + DISPLAY("display"), + CAMERA("camera"); + + private final String name; + + VideoSource(String name) { + this.name = name; + } + + public static VideoSource findByName(String name) { + for (VideoSource videoSource : VideoSource.values()) { + if (name.equals(videoSource.name)) { + return videoSource; + } + } + + return null; + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/video/VirtualDisplayListener.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/video/VirtualDisplayListener.java new file mode 100644 index 0000000..c079265 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/video/VirtualDisplayListener.java @@ -0,0 +1,7 @@ +package com.genymobile.scrcpy.video; + +import com.genymobile.scrcpy.control.PositionMapper; + +public interface VirtualDisplayListener { + void onNewVirtualDisplay(int displayId, PositionMapper positionMapper); +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/wrappers/ActivityManager.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/wrappers/ActivityManager.java new file mode 100644 index 0000000..0150888 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/wrappers/ActivityManager.java @@ -0,0 +1,192 @@ +package com.genymobile.scrcpy.wrappers; + +import com.genymobile.scrcpy.AndroidVersions; +import com.genymobile.scrcpy.FakeContext; +import com.genymobile.scrcpy.util.Ln; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.content.IContentProvider; +import android.content.Intent; +import android.os.Binder; +import android.os.Bundle; +import android.os.IBinder; +import android.os.IInterface; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +@SuppressLint("PrivateApi,DiscouragedPrivateApi") +public final class ActivityManager { + + private final IInterface manager; + private Method getContentProviderExternalMethod; + private boolean getContentProviderExternalMethodNewVersion = true; + private Method removeContentProviderExternalMethod; + private Method startActivityAsUserMethod; + private Method forceStopPackageMethod; + private Method broadcastIntentMethod; + + static ActivityManager create() { + try { + // On old Android versions, the ActivityManager is not exposed via AIDL, + // so use ActivityManagerNative.getDefault() + Class cls = Class.forName("android.app.ActivityManagerNative"); + Method getDefaultMethod = cls.getDeclaredMethod("getDefault"); + IInterface am = (IInterface) getDefaultMethod.invoke(null); + return new ActivityManager(am); + } catch (ReflectiveOperationException e) { + throw new AssertionError(e); + } + } + + private ActivityManager(IInterface manager) { + this.manager = manager; + } + + private Method getGetContentProviderExternalMethod() throws NoSuchMethodException { + if (getContentProviderExternalMethod == null) { + try { + getContentProviderExternalMethod = manager.getClass() + .getMethod("getContentProviderExternal", String.class, int.class, IBinder.class, String.class); + } catch (NoSuchMethodException e) { + // old version + getContentProviderExternalMethod = manager.getClass().getMethod("getContentProviderExternal", String.class, int.class, IBinder.class); + getContentProviderExternalMethodNewVersion = false; + } + } + return getContentProviderExternalMethod; + } + + private Method getRemoveContentProviderExternalMethod() throws NoSuchMethodException { + if (removeContentProviderExternalMethod == null) { + removeContentProviderExternalMethod = manager.getClass().getMethod("removeContentProviderExternal", String.class, IBinder.class); + } + return removeContentProviderExternalMethod; + } + + @TargetApi(AndroidVersions.API_29_ANDROID_10) + public IContentProvider getContentProviderExternal(String name, IBinder token) { + try { + Method method = getGetContentProviderExternalMethod(); + Object[] args; + if (getContentProviderExternalMethodNewVersion) { + // new version + args = new Object[]{name, FakeContext.ROOT_UID, token, null}; + } else { + // old version + args = new Object[]{name, FakeContext.ROOT_UID, token}; + } + // ContentProviderHolder providerHolder = getContentProviderExternal(...); + Object providerHolder = method.invoke(manager, args); + if (providerHolder == null) { + return null; + } + // IContentProvider provider = providerHolder.provider; + Field providerField = providerHolder.getClass().getDeclaredField("provider"); + providerField.setAccessible(true); + return (IContentProvider) providerField.get(providerHolder); + } catch (ReflectiveOperationException e) { + Ln.e("Could not invoke method", e); + return null; + } + } + + void removeContentProviderExternal(String name, IBinder token) { + try { + Method method = getRemoveContentProviderExternalMethod(); + method.invoke(manager, name, token); + } catch (ReflectiveOperationException e) { + Ln.e("Could not invoke method", e); + } + } + + public ContentProvider createSettingsProvider() { + IBinder token = new Binder(); + IContentProvider provider = getContentProviderExternal("settings", token); + if (provider == null) { + return null; + } + return new ContentProvider(this, provider, "settings", token); + } + + private Method getStartActivityAsUserMethod() throws NoSuchMethodException, ClassNotFoundException { + if (startActivityAsUserMethod == null) { + Class iApplicationThreadClass = Class.forName("android.app.IApplicationThread"); + Class profilerInfo = Class.forName("android.app.ProfilerInfo"); + startActivityAsUserMethod = manager.getClass() + .getMethod("startActivityAsUser", iApplicationThreadClass, String.class, Intent.class, String.class, IBinder.class, String.class, + int.class, int.class, profilerInfo, Bundle.class, int.class); + } + return startActivityAsUserMethod; + } + + public int startActivity(Intent intent) { + return startActivity(intent, null); + } + + @SuppressWarnings("ConstantConditions") + public int startActivity(Intent intent, Bundle options) { + try { + Method method = getStartActivityAsUserMethod(); + return (int) method.invoke( + /* this */ manager, + /* caller */ null, + /* callingPackage */ FakeContext.PACKAGE_NAME, + /* intent */ intent, + /* resolvedType */ null, + /* resultTo */ null, + /* resultWho */ null, + /* requestCode */ 0, + /* startFlags */ 0, + /* profilerInfo */ null, + /* bOptions */ options, + /* userId */ /* UserHandle.USER_CURRENT */ -2); + } catch (Throwable e) { + Ln.e("Could not invoke method", e); + return 0; + } + } + + private Method getForceStopPackageMethod() throws NoSuchMethodException { + if (forceStopPackageMethod == null) { + forceStopPackageMethod = manager.getClass().getMethod("forceStopPackage", String.class, int.class); + } + return forceStopPackageMethod; + } + + public void forceStopPackage(String packageName) { + try { + Method method = getForceStopPackageMethod(); + method.invoke(manager, packageName, /* userId */ /* UserHandle.USER_CURRENT */ -2); + } catch (Throwable e) { + Ln.e("Could not invoke method", e); + } + } + + private Method getBroadcastIntentMethod() throws NoSuchMethodException { + if (broadcastIntentMethod == null) { + try { + Class iApplicationThreadClass = Class.forName("android.app.IApplicationThread"); + Class iIntentReceiverClass = Class.forName("android.content.IIntentReceiver"); + broadcastIntentMethod = manager.getClass() + .getMethod("broadcastIntent", iApplicationThreadClass, Intent.class, String.class, iIntentReceiverClass, int.class, + String.class, Bundle.class, String[].class, int.class, Bundle.class, boolean.class, boolean.class, int.class); + } catch (ClassNotFoundException e) { + throw new AssertionError(e); + } + } + return broadcastIntentMethod; + } + + public void sendBroadcast(Intent intent) { + try { + // Equivalent to: + // adb shell am broadcast -a android.intent.action.MEDIA_SCANNER_SCAN_FILE -d file:// + Method method = getBroadcastIntentMethod(); + method.invoke(manager, null, intent, null, null, 0, null, null, null, -1, null, true, false, /* userId */ -2); + } catch (Throwable e) { + Ln.e("Could not invoke method", e); + } + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/wrappers/ClipboardManager.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/wrappers/ClipboardManager.java new file mode 100644 index 0000000..5493612 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/wrappers/ClipboardManager.java @@ -0,0 +1,43 @@ +package com.genymobile.scrcpy.wrappers; + +import com.genymobile.scrcpy.FakeContext; + +import android.content.ClipData; +import android.content.Context; + +public final class ClipboardManager { + private final android.content.ClipboardManager manager; + + static ClipboardManager create() { + android.content.ClipboardManager manager = (android.content.ClipboardManager) FakeContext.get().getSystemService(Context.CLIPBOARD_SERVICE); + if (manager == null) { + // Some devices have no clipboard manager + // + // + return null; + } + return new ClipboardManager(manager); + } + + private ClipboardManager(android.content.ClipboardManager manager) { + this.manager = manager; + } + + public CharSequence getText() { + ClipData clipData = manager.getPrimaryClip(); + if (clipData == null || clipData.getItemCount() == 0) { + return null; + } + return clipData.getItemAt(0).getText(); + } + + public boolean setText(CharSequence text) { + ClipData clipData = ClipData.newPlainText(null, text); + manager.setPrimaryClip(clipData); + return true; + } + + public void addPrimaryClipChangedListener(android.content.ClipboardManager.OnPrimaryClipChangedListener listener) { + manager.addPrimaryClipChangedListener(listener); + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/wrappers/ContentProvider.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/wrappers/ContentProvider.java new file mode 100644 index 0000000..f625b39 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/wrappers/ContentProvider.java @@ -0,0 +1,162 @@ +package com.genymobile.scrcpy.wrappers; + +import com.genymobile.scrcpy.AndroidVersions; +import com.genymobile.scrcpy.FakeContext; +import com.genymobile.scrcpy.util.Ln; +import com.genymobile.scrcpy.util.SettingsException; + +import android.annotation.SuppressLint; +import android.content.AttributionSource; +import android.os.Build; +import android.os.Bundle; +import android.os.IBinder; + +import java.io.Closeable; +import java.lang.reflect.Method; + +public final class ContentProvider implements Closeable { + + public static final String TABLE_SYSTEM = "system"; + public static final String TABLE_SECURE = "secure"; + public static final String TABLE_GLOBAL = "global"; + + // See android/providerHolder/Settings.java + private static final String CALL_METHOD_GET_SYSTEM = "GET_system"; + private static final String CALL_METHOD_GET_SECURE = "GET_secure"; + private static final String CALL_METHOD_GET_GLOBAL = "GET_global"; + + private static final String CALL_METHOD_PUT_SYSTEM = "PUT_system"; + private static final String CALL_METHOD_PUT_SECURE = "PUT_secure"; + private static final String CALL_METHOD_PUT_GLOBAL = "PUT_global"; + + private static final String CALL_METHOD_USER_KEY = "_user"; + + private static final String NAME_VALUE_TABLE_VALUE = "value"; + + private final ActivityManager manager; + // android.content.IContentProvider + private final Object provider; + private final String name; + private final IBinder token; + + private Method callMethod; + private int callMethodVersion; + + ContentProvider(ActivityManager manager, Object provider, String name, IBinder token) { + this.manager = manager; + this.provider = provider; + this.name = name; + this.token = token; + } + + @SuppressLint("PrivateApi") + private Method getCallMethod() throws NoSuchMethodException { + if (callMethod == null) { + if (Build.VERSION.SDK_INT >= AndroidVersions.API_31_ANDROID_12) { + callMethod = provider.getClass().getMethod("call", AttributionSource.class, String.class, String.class, String.class, Bundle.class); + callMethodVersion = 0; + } else { + // old versions + try { + callMethod = provider.getClass() + .getMethod("call", String.class, String.class, String.class, String.class, String.class, Bundle.class); + callMethodVersion = 1; + } catch (NoSuchMethodException e1) { + try { + callMethod = provider.getClass().getMethod("call", String.class, String.class, String.class, String.class, Bundle.class); + callMethodVersion = 2; + } catch (NoSuchMethodException e2) { + callMethod = provider.getClass().getMethod("call", String.class, String.class, String.class, Bundle.class); + callMethodVersion = 3; + } + } + } + } + return callMethod; + } + + private Bundle call(String callMethod, String arg, Bundle extras) throws ReflectiveOperationException { + try { + Method method = getCallMethod(); + Object[] args; + + if (Build.VERSION.SDK_INT >= AndroidVersions.API_31_ANDROID_12 && callMethodVersion == 0) { + args = new Object[]{FakeContext.get().getAttributionSource(), "settings", callMethod, arg, extras}; + } else { + switch (callMethodVersion) { + case 1: + args = new Object[]{FakeContext.PACKAGE_NAME, null, "settings", callMethod, arg, extras}; + break; + case 2: + args = new Object[]{FakeContext.PACKAGE_NAME, "settings", callMethod, arg, extras}; + break; + default: + args = new Object[]{FakeContext.PACKAGE_NAME, callMethod, arg, extras}; + break; + } + } + return (Bundle) method.invoke(provider, args); + } catch (ReflectiveOperationException e) { + Ln.e("Could not invoke method", e); + throw e; + } + } + + public void close() { + manager.removeContentProviderExternal(name, token); + } + + private static String getGetMethod(String table) { + switch (table) { + case TABLE_SECURE: + return CALL_METHOD_GET_SECURE; + case TABLE_SYSTEM: + return CALL_METHOD_GET_SYSTEM; + case TABLE_GLOBAL: + return CALL_METHOD_GET_GLOBAL; + default: + throw new IllegalArgumentException("Invalid table: " + table); + } + } + + private static String getPutMethod(String table) { + switch (table) { + case TABLE_SECURE: + return CALL_METHOD_PUT_SECURE; + case TABLE_SYSTEM: + return CALL_METHOD_PUT_SYSTEM; + case TABLE_GLOBAL: + return CALL_METHOD_PUT_GLOBAL; + default: + throw new IllegalArgumentException("Invalid table: " + table); + } + } + + public String getValue(String table, String key) throws SettingsException { + String method = getGetMethod(table); + Bundle arg = new Bundle(); + arg.putInt(CALL_METHOD_USER_KEY, FakeContext.ROOT_UID); + try { + Bundle bundle = call(method, key, arg); + if (bundle == null) { + return null; + } + return bundle.getString("value"); + } catch (Exception e) { + throw new SettingsException(table, "get", key, null, e); + } + + } + + public void putValue(String table, String key, String value) throws SettingsException { + String method = getPutMethod(table); + Bundle arg = new Bundle(); + arg.putInt(CALL_METHOD_USER_KEY, FakeContext.ROOT_UID); + arg.putString(NAME_VALUE_TABLE_VALUE, value); + try { + call(method, key, arg); + } catch (Exception e) { + throw new SettingsException(table, "put", key, value, e); + } + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/wrappers/DisplayControl.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/wrappers/DisplayControl.java new file mode 100644 index 0000000..88ca3d3 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/wrappers/DisplayControl.java @@ -0,0 +1,82 @@ +package com.genymobile.scrcpy.wrappers; + +import com.genymobile.scrcpy.AndroidVersions; +import com.genymobile.scrcpy.util.Ln; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.os.IBinder; +import android.system.Os; + +import java.lang.reflect.Method; + +@SuppressLint({"PrivateApi", "SoonBlockedPrivateApi", "BlockedPrivateApi"}) +@TargetApi(AndroidVersions.API_34_ANDROID_14) +public final class DisplayControl { + + private static final Class CLASS; + + static { + Class displayControlClass = null; + try { + Class classLoaderFactoryClass = Class.forName("com.android.internal.os.ClassLoaderFactory"); + Method createClassLoaderMethod = classLoaderFactoryClass.getDeclaredMethod("createClassLoader", String.class, String.class, String.class, + ClassLoader.class, int.class, boolean.class, String.class); + + String systemServerClasspath = Os.getenv("SYSTEMSERVERCLASSPATH"); + ClassLoader classLoader = (ClassLoader) createClassLoaderMethod.invoke(null, systemServerClasspath, null, null, + ClassLoader.getSystemClassLoader(), 0, true, null); + + displayControlClass = classLoader.loadClass("com.android.server.display.DisplayControl"); + + Method loadMethod = Runtime.class.getDeclaredMethod("loadLibrary0", Class.class, String.class); + loadMethod.setAccessible(true); + loadMethod.invoke(Runtime.getRuntime(), displayControlClass, "android_servers"); + } catch (Throwable e) { + Ln.e("Could not initialize DisplayControl", e); + // Do not throw an exception here, the methods will fail when they are called + } + CLASS = displayControlClass; + } + + private static Method getPhysicalDisplayTokenMethod; + private static Method getPhysicalDisplayIdsMethod; + + private DisplayControl() { + // only static methods + } + + private static Method getGetPhysicalDisplayTokenMethod() throws NoSuchMethodException { + if (getPhysicalDisplayTokenMethod == null) { + getPhysicalDisplayTokenMethod = CLASS.getMethod("getPhysicalDisplayToken", long.class); + } + return getPhysicalDisplayTokenMethod; + } + + public static IBinder getPhysicalDisplayToken(long physicalDisplayId) { + try { + Method method = getGetPhysicalDisplayTokenMethod(); + return (IBinder) method.invoke(null, physicalDisplayId); + } catch (ReflectiveOperationException e) { + Ln.e("Could not invoke method", e); + return null; + } + } + + private static Method getGetPhysicalDisplayIdsMethod() throws NoSuchMethodException { + if (getPhysicalDisplayIdsMethod == null) { + getPhysicalDisplayIdsMethod = CLASS.getMethod("getPhysicalDisplayIds"); + } + return getPhysicalDisplayIdsMethod; + } + + public static long[] getPhysicalDisplayIds() { + try { + Method method = getGetPhysicalDisplayIdsMethod(); + return (long[]) method.invoke(null); + } catch (ReflectiveOperationException e) { + Ln.e("Could not invoke method", e); + return null; + } + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/wrappers/DisplayManager.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/wrappers/DisplayManager.java new file mode 100644 index 0000000..1be04c3 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/wrappers/DisplayManager.java @@ -0,0 +1,250 @@ +package com.genymobile.scrcpy.wrappers; + +import com.genymobile.scrcpy.AndroidVersions; +import com.genymobile.scrcpy.FakeContext; +import com.genymobile.scrcpy.display.DisplayInfo; +import com.genymobile.scrcpy.model.Size; +import com.genymobile.scrcpy.util.Command; +import com.genymobile.scrcpy.util.Ln; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.content.Context; +import android.hardware.display.VirtualDisplay; +import android.os.Handler; +import android.view.Display; +import android.view.Surface; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@SuppressLint("PrivateApi,DiscouragedPrivateApi") +public final class DisplayManager { + + // android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_CHANGED + public static final long EVENT_FLAG_DISPLAY_CHANGED = 1L << 2; + + public interface DisplayListener { + /** + * Called whenever the properties of a logical {@link android.view.Display}, + * such as size and density, have changed. + * + * @param displayId The id of the logical display that changed. + */ + void onDisplayChanged(int displayId); + } + + public static final class DisplayListenerHandle { + private final Object displayListenerProxy; + private DisplayListenerHandle(Object displayListenerProxy) { + this.displayListenerProxy = displayListenerProxy; + } + } + + private final Object manager; // instance of hidden class android.hardware.display.DisplayManagerGlobal + private Method getDisplayInfoMethod; + private Method createVirtualDisplayMethod; + private Method requestDisplayPowerMethod; + + static DisplayManager create() { + try { + Class clazz = Class.forName("android.hardware.display.DisplayManagerGlobal"); + Method getInstanceMethod = clazz.getDeclaredMethod("getInstance"); + Object dmg = getInstanceMethod.invoke(null); + return new DisplayManager(dmg); + } catch (ReflectiveOperationException e) { + throw new AssertionError(e); + } + } + + private DisplayManager(Object manager) { + this.manager = manager; + } + + // public to call it from unit tests + public static DisplayInfo parseDisplayInfo(String dumpsysDisplayOutput, int displayId) { + Pattern regex = Pattern.compile( + "^ mOverrideDisplayInfo=DisplayInfo\\{\".*?, displayId " + displayId + ".*?(, FLAG_.*)?, real ([0-9]+) x ([0-9]+).*?, " + + "rotation ([0-9]+).*?, density ([0-9]+).*?, layerStack ([0-9]+)", + Pattern.MULTILINE); + Matcher m = regex.matcher(dumpsysDisplayOutput); + if (!m.find()) { + return null; + } + int flags = parseDisplayFlags(m.group(1)); + int width = Integer.parseInt(m.group(2)); + int height = Integer.parseInt(m.group(3)); + int rotation = Integer.parseInt(m.group(4)); + int density = Integer.parseInt(m.group(5)); + int layerStack = Integer.parseInt(m.group(6)); + + return new DisplayInfo(displayId, new Size(width, height), rotation, layerStack, flags, density, null); + } + + private static DisplayInfo getDisplayInfoFromDumpsysDisplay(int displayId) { + try { + String dumpsysDisplayOutput = Command.execReadOutput("dumpsys", "display"); + return parseDisplayInfo(dumpsysDisplayOutput, displayId); + } catch (Exception e) { + Ln.e("Could not get display info from \"dumpsys display\" output", e); + return null; + } + } + + private static int parseDisplayFlags(String text) { + if (text == null) { + return 0; + } + + int flags = 0; + Pattern regex = Pattern.compile("FLAG_[A-Z_]+"); + Matcher m = regex.matcher(text); + while (m.find()) { + String flagString = m.group(); + try { + Field filed = Display.class.getDeclaredField(flagString); + flags |= filed.getInt(null); + } catch (ReflectiveOperationException e) { + // Silently ignore, some flags reported by "dumpsys display" are @TestApi + } + } + return flags; + } + + // getDisplayInfo() may be used from both the Controller thread and the video (main) thread + private synchronized Method getGetDisplayInfoMethod() throws NoSuchMethodException { + if (getDisplayInfoMethod == null) { + getDisplayInfoMethod = manager.getClass().getMethod("getDisplayInfo", int.class); + } + return getDisplayInfoMethod; + } + + public DisplayInfo getDisplayInfo(int displayId) { + try { + Method method = getGetDisplayInfoMethod(); + Object displayInfo = method.invoke(manager, displayId); + if (displayInfo == null) { + // fallback when displayInfo is null + return getDisplayInfoFromDumpsysDisplay(displayId); + } + Class cls = displayInfo.getClass(); + // width and height already take the rotation into account + int width = cls.getDeclaredField("logicalWidth").getInt(displayInfo); + int height = cls.getDeclaredField("logicalHeight").getInt(displayInfo); + int rotation = cls.getDeclaredField("rotation").getInt(displayInfo); + int layerStack = cls.getDeclaredField("layerStack").getInt(displayInfo); + int flags = cls.getDeclaredField("flags").getInt(displayInfo); + int dpi = cls.getDeclaredField("logicalDensityDpi").getInt(displayInfo); + String uniqueId; + try { + uniqueId = (String) cls.getDeclaredField("uniqueId").get(displayInfo); + } catch (NoSuchFieldException e) { + // This field might not exist: + uniqueId = null; + } + return new DisplayInfo(displayId, new Size(width, height), rotation, layerStack, flags, dpi, uniqueId); + } catch (ReflectiveOperationException e) { + throw new AssertionError(e); + } + } + + public int[] getDisplayIds() { + try { + return (int[]) manager.getClass().getMethod("getDisplayIds").invoke(manager); + } catch (ReflectiveOperationException e) { + throw new AssertionError(e); + } + } + + private Method getCreateVirtualDisplayMethod() throws NoSuchMethodException { + if (createVirtualDisplayMethod == null) { + createVirtualDisplayMethod = android.hardware.display.DisplayManager.class + .getMethod("createVirtualDisplay", String.class, int.class, int.class, int.class, Surface.class); + } + return createVirtualDisplayMethod; + } + + public VirtualDisplay createVirtualDisplay(String name, int width, int height, int displayIdToMirror, Surface surface) throws Exception { + Method method = getCreateVirtualDisplayMethod(); + return (VirtualDisplay) method.invoke(null, name, width, height, displayIdToMirror, surface); + } + + public VirtualDisplay createNewVirtualDisplay(String name, int width, int height, int dpi, Surface surface, int flags) throws Exception { + Constructor ctor = android.hardware.display.DisplayManager.class.getDeclaredConstructor( + Context.class); + ctor.setAccessible(true); + android.hardware.display.DisplayManager dm = ctor.newInstance(FakeContext.get()); + return dm.createVirtualDisplay(name, width, height, dpi, surface, flags); + } + + private Method getRequestDisplayPowerMethod() throws NoSuchMethodException { + if (requestDisplayPowerMethod == null) { + requestDisplayPowerMethod = manager.getClass().getMethod("requestDisplayPower", int.class, boolean.class); + } + return requestDisplayPowerMethod; + } + + @TargetApi(AndroidVersions.API_35_ANDROID_15) + public boolean requestDisplayPower(int displayId, boolean on) { + try { + Method method = getRequestDisplayPowerMethod(); + return (boolean) method.invoke(manager, displayId, on); + } catch (ReflectiveOperationException e) { + Ln.e("Could not invoke method", e); + return false; + } + } + + public DisplayListenerHandle registerDisplayListener(DisplayListener listener, Handler handler) { + try { + Class displayListenerClass = Class.forName("android.hardware.display.DisplayManager$DisplayListener"); + Object displayListenerProxy = Proxy.newProxyInstance( + ClassLoader.getSystemClassLoader(), + new Class[] {displayListenerClass}, + (proxy, method, args) -> { + if ("onDisplayChanged".equals(method.getName())) { + listener.onDisplayChanged((int) args[0]); + } + if ("toString".equals(method.getName())) { + return "DisplayListener"; + } + return null; + }); + try { + manager.getClass() + .getMethod("registerDisplayListener", displayListenerClass, Handler.class, long.class, String.class) + .invoke(manager, displayListenerProxy, handler, EVENT_FLAG_DISPLAY_CHANGED, FakeContext.PACKAGE_NAME); + } catch (NoSuchMethodException e) { + try { + manager.getClass() + .getMethod("registerDisplayListener", displayListenerClass, Handler.class, long.class) + .invoke(manager, displayListenerProxy, handler, EVENT_FLAG_DISPLAY_CHANGED); + } catch (NoSuchMethodException e2) { + manager.getClass() + .getMethod("registerDisplayListener", displayListenerClass, Handler.class) + .invoke(manager, displayListenerProxy, handler); + } + } + + return new DisplayListenerHandle(displayListenerProxy); + } catch (Exception e) { + // Rotation and screen size won't be updated, not a fatal error + Ln.e("Could not register display listener", e); + } + + return null; + } + + public void unregisterDisplayListener(DisplayListenerHandle listener) { + try { + Class displayListenerClass = Class.forName("android.hardware.display.DisplayManager$DisplayListener"); + manager.getClass().getMethod("unregisterDisplayListener", displayListenerClass).invoke(manager, listener.displayListenerProxy); + } catch (Exception e) { + Ln.e("Could not unregister display listener", e); + } + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/wrappers/DisplayWindowListener.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/wrappers/DisplayWindowListener.java new file mode 100644 index 0000000..1573d81 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/wrappers/DisplayWindowListener.java @@ -0,0 +1,37 @@ +package com.genymobile.scrcpy.wrappers; + +import com.genymobile.scrcpy.util.Ln; + +import android.content.res.Configuration; +import android.os.Parcel; +import android.os.RemoteException; +import android.view.IDisplayWindowListener; + +public class DisplayWindowListener extends IDisplayWindowListener.Stub { + @Override + public void onDisplayAdded(int displayId) { + // empty default implementation + } + + @Override + public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) { + // empty default implementation + } + + @Override + public void onDisplayRemoved(int displayId) { + // empty default implementation + } + + @Override + public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { + try { + return super.onTransact(code, data, reply, flags); + } catch (AbstractMethodError e) { + Ln.v("Ignoring AbstractMethodError: " + e.getMessage()); + // Ignore unknown methods, write default response to reply parcel + reply.writeNoException(); + return true; + } + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/wrappers/InputManager.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/wrappers/InputManager.java new file mode 100644 index 0000000..f55648d --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/wrappers/InputManager.java @@ -0,0 +1,146 @@ +package com.genymobile.scrcpy.wrappers; + +import com.genymobile.scrcpy.AndroidVersions; +import com.genymobile.scrcpy.FakeContext; +import com.genymobile.scrcpy.util.Ln; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.view.InputEvent; +import android.view.MotionEvent; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +@SuppressLint("PrivateApi,DiscouragedPrivateApi") +public final class InputManager { + + public static final int INJECT_INPUT_EVENT_MODE_ASYNC = 0; + public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT = 1; + public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH = 2; + + private final android.hardware.input.InputManager manager; + private long lastPermissionLogDate; + + private static Method injectInputEventMethod; + private static Method setDisplayIdMethod; + private static Method setActionButtonMethod; + private static Method addUniqueIdAssociationByPortMethod; + private static Method removeUniqueIdAssociationByPortMethod; + + static InputManager create() { + android.hardware.input.InputManager manager = (android.hardware.input.InputManager) FakeContext.get() + .getSystemService(FakeContext.INPUT_SERVICE); + return new InputManager(manager); + } + + private InputManager(android.hardware.input.InputManager manager) { + this.manager = manager; + } + + private static Method getInjectInputEventMethod() throws NoSuchMethodException { + if (injectInputEventMethod == null) { + injectInputEventMethod = android.hardware.input.InputManager.class.getMethod("injectInputEvent", InputEvent.class, int.class); + } + return injectInputEventMethod; + } + + public boolean injectInputEvent(InputEvent inputEvent, int mode) { + try { + Method method = getInjectInputEventMethod(); + return (boolean) method.invoke(manager, inputEvent, mode); + } catch (ReflectiveOperationException e) { + if (e instanceof InvocationTargetException) { + Throwable cause = e.getCause(); + if (cause instanceof SecurityException) { + String message = e.getCause().getMessage(); + if (message != null && message.contains("INJECT_EVENTS permission")) { + // Do not flood the console, limit to one permission error log every 3 seconds + long now = System.currentTimeMillis(); + if (lastPermissionLogDate <= now - 3000) { + Ln.e(message); + Ln.e("Make sure you have enabled \"USB debugging (Security Settings)\" and then rebooted your device."); + lastPermissionLogDate = now; + } + // Do not print the stack trace + return false; + } + } + } + Ln.e("Could not invoke method", e); + return false; + } + } + + private static Method getSetDisplayIdMethod() throws NoSuchMethodException { + if (setDisplayIdMethod == null) { + setDisplayIdMethod = InputEvent.class.getMethod("setDisplayId", int.class); + } + return setDisplayIdMethod; + } + + public static boolean setDisplayId(InputEvent inputEvent, int displayId) { + try { + Method method = getSetDisplayIdMethod(); + method.invoke(inputEvent, displayId); + return true; + } catch (ReflectiveOperationException e) { + Ln.e("Cannot associate a display id to the input event", e); + return false; + } + } + + private static Method getSetActionButtonMethod() throws NoSuchMethodException { + if (setActionButtonMethod == null) { + setActionButtonMethod = MotionEvent.class.getMethod("setActionButton", int.class); + } + return setActionButtonMethod; + } + + public static boolean setActionButton(MotionEvent motionEvent, int actionButton) { + try { + Method method = getSetActionButtonMethod(); + method.invoke(motionEvent, actionButton); + return true; + } catch (ReflectiveOperationException e) { + Ln.e("Cannot set action button on MotionEvent", e); + return false; + } + } + + private static Method getAddUniqueIdAssociationByPortMethod() throws NoSuchMethodException { + if (addUniqueIdAssociationByPortMethod == null) { + addUniqueIdAssociationByPortMethod = android.hardware.input.InputManager.class.getMethod( + "addUniqueIdAssociationByPort", String.class, String.class); + } + return addUniqueIdAssociationByPortMethod; + } + + @TargetApi(AndroidVersions.API_35_ANDROID_15) + public void addUniqueIdAssociationByPort(String inputPort, String uniqueId) { + try { + Method method = getAddUniqueIdAssociationByPortMethod(); + method.invoke(manager, inputPort, uniqueId); + } catch (ReflectiveOperationException e) { + Ln.e("Cannot add unique id association by port", e); + } + } + + private static Method getRemoveUniqueIdAssociationByPortMethod() throws NoSuchMethodException { + if (removeUniqueIdAssociationByPortMethod == null) { + removeUniqueIdAssociationByPortMethod = android.hardware.input.InputManager.class.getMethod( + "removeUniqueIdAssociationByPort", String.class); + } + return removeUniqueIdAssociationByPortMethod; + } + + @TargetApi(AndroidVersions.API_35_ANDROID_15) + public void removeUniqueIdAssociationByPort(String inputPort) { + try { + Method method = getRemoveUniqueIdAssociationByPortMethod(); + method.invoke(manager, inputPort); + } catch (ReflectiveOperationException e) { + Ln.e("Cannot remove unique id association by port", e); + } + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/wrappers/PowerManager.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/wrappers/PowerManager.java new file mode 100644 index 0000000..c7ba3f1 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/wrappers/PowerManager.java @@ -0,0 +1,80 @@ +package com.genymobile.scrcpy.wrappers; + +import com.genymobile.scrcpy.AndroidVersions; +import com.genymobile.scrcpy.util.Ln; + +import android.os.Build; +import android.os.IInterface; +import android.os.SystemClock; + +import java.lang.reflect.Method; + +public final class PowerManager { + + private static final int USER_ACTIVITY_EVENT_OTHER = 0; + + private final IInterface manager; + private Method isScreenOnMethod; + private Method userActivityMethod; + + static PowerManager create() { + IInterface manager = ServiceManager.getService("power", "android.os.IPowerManager"); + return new PowerManager(manager); + } + + private PowerManager(IInterface manager) { + this.manager = manager; + } + + private Method getIsScreenOnMethod() throws NoSuchMethodException { + if (isScreenOnMethod == null) { + if (Build.VERSION.SDK_INT >= AndroidVersions.API_34_ANDROID_14) { + isScreenOnMethod = manager.getClass().getMethod("isDisplayInteractive", int.class); + } else { + isScreenOnMethod = manager.getClass().getMethod("isInteractive"); + } + } + return isScreenOnMethod; + } + + public boolean isScreenOn(int displayId) { + + try { + Method method = getIsScreenOnMethod(); + if (Build.VERSION.SDK_INT >= AndroidVersions.API_34_ANDROID_14) { + return (boolean) method.invoke(manager, displayId); + } + return (boolean) method.invoke(manager); + } catch (ReflectiveOperationException e) { + Ln.e("Could not invoke method", e); + return false; + } + } + + private Method getUserActivityMethod() throws NoSuchMethodException { + if (userActivityMethod == null) { + if (Build.VERSION.SDK_INT >= AndroidVersions.API_31_ANDROID_12) { + // userActivity(int displayId, long time, int event, int flags); + userActivityMethod = manager.getClass().getMethod("userActivity", int.class, long.class, int.class, int.class); + } else { + // userActivity(long time, int event, int flags); + userActivityMethod = manager.getClass().getMethod("userActivity", long.class, int.class, int.class); + } + } + return userActivityMethod; + } + + public void userActivity(int displayId) { + try { + Method method = getUserActivityMethod(); + long time = SystemClock.uptimeMillis(); + if (Build.VERSION.SDK_INT >= AndroidVersions.API_31_ANDROID_12) { + method.invoke(manager, displayId, time, USER_ACTIVITY_EVENT_OTHER, 0); + return; + } + method.invoke(manager, time, USER_ACTIVITY_EVENT_OTHER, 0); + } catch (ReflectiveOperationException e) { + Ln.e("Could not invoke method", e); + } + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/wrappers/ServiceManager.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/wrappers/ServiceManager.java new file mode 100644 index 0000000..b1123b5 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/wrappers/ServiceManager.java @@ -0,0 +1,112 @@ +package com.genymobile.scrcpy.wrappers; + +import com.genymobile.scrcpy.FakeContext; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.hardware.camera2.CameraManager; +import android.os.IBinder; +import android.os.IInterface; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +@SuppressLint("PrivateApi,DiscouragedPrivateApi") +public final class ServiceManager { + + private static final Method GET_SERVICE_METHOD; + + static { + try { + GET_SERVICE_METHOD = Class.forName("android.os.ServiceManager").getDeclaredMethod("getService", String.class); + } catch (Exception e) { + throw new AssertionError(e); + } + } + + private static WindowManager windowManager; + private static DisplayManager displayManager; + private static InputManager inputManager; + private static PowerManager powerManager; + private static StatusBarManager statusBarManager; + private static ClipboardManager clipboardManager; + private static ActivityManager activityManager; + private static CameraManager cameraManager; + + private ServiceManager() { + /* not instantiable */ + } + + static IInterface getService(String service, String type) { + try { + IBinder binder = (IBinder) GET_SERVICE_METHOD.invoke(null, service); + Method asInterfaceMethod = Class.forName(type + "$Stub").getMethod("asInterface", IBinder.class); + return (IInterface) asInterfaceMethod.invoke(null, binder); + } catch (Exception e) { + throw new AssertionError(e); + } + } + + public static WindowManager getWindowManager() { + if (windowManager == null) { + windowManager = WindowManager.create(); + } + return windowManager; + } + + // The DisplayManager may be used from both the Controller thread and the video (main) thread + public static synchronized DisplayManager getDisplayManager() { + if (displayManager == null) { + displayManager = DisplayManager.create(); + } + return displayManager; + } + + public static InputManager getInputManager() { + if (inputManager == null) { + inputManager = InputManager.create(); + } + return inputManager; + } + + public static PowerManager getPowerManager() { + if (powerManager == null) { + powerManager = PowerManager.create(); + } + return powerManager; + } + + public static StatusBarManager getStatusBarManager() { + if (statusBarManager == null) { + statusBarManager = StatusBarManager.create(); + } + return statusBarManager; + } + + public static ClipboardManager getClipboardManager() { + if (clipboardManager == null) { + // May be null, some devices have no clipboard manager + clipboardManager = ClipboardManager.create(); + } + return clipboardManager; + } + + public static ActivityManager getActivityManager() { + if (activityManager == null) { + activityManager = ActivityManager.create(); + } + return activityManager; + } + + public static CameraManager getCameraManager() { + if (cameraManager == null) { + try { + Constructor ctor = CameraManager.class.getDeclaredConstructor(Context.class); + cameraManager = ctor.newInstance(FakeContext.get()); + } catch (Exception e) { + throw new AssertionError(e); + } + } + return cameraManager; + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/wrappers/StatusBarManager.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/wrappers/StatusBarManager.java new file mode 100644 index 0000000..ca80dde --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/wrappers/StatusBarManager.java @@ -0,0 +1,97 @@ +package com.genymobile.scrcpy.wrappers; + +import com.genymobile.scrcpy.util.Ln; + +import android.os.IInterface; + +import java.lang.reflect.Method; + +public final class StatusBarManager { + + private final IInterface manager; + private Method expandNotificationsPanelMethod; + private boolean expandNotificationPanelMethodCustomVersion; + private Method expandSettingsPanelMethod; + private boolean expandSettingsPanelMethodNewVersion = true; + private Method collapsePanelsMethod; + + static StatusBarManager create() { + IInterface manager = ServiceManager.getService("statusbar", "com.android.internal.statusbar.IStatusBarService"); + return new StatusBarManager(manager); + } + + private StatusBarManager(IInterface manager) { + this.manager = manager; + } + + private Method getExpandNotificationsPanelMethod() throws NoSuchMethodException { + if (expandNotificationsPanelMethod == null) { + try { + expandNotificationsPanelMethod = manager.getClass().getMethod("expandNotificationsPanel"); + } catch (NoSuchMethodException e) { + // Custom version for custom vendor ROM: + expandNotificationsPanelMethod = manager.getClass().getMethod("expandNotificationsPanel", int.class); + expandNotificationPanelMethodCustomVersion = true; + } + } + return expandNotificationsPanelMethod; + } + + private Method getExpandSettingsPanel() throws NoSuchMethodException { + if (expandSettingsPanelMethod == null) { + try { + // Since Android 7: https://android.googlesource.com/platform/frameworks/base.git/+/a9927325eda025504d59bb6594fee8e240d95b01%5E%21/ + expandSettingsPanelMethod = manager.getClass().getMethod("expandSettingsPanel", String.class); + } catch (NoSuchMethodException e) { + // old version + expandSettingsPanelMethod = manager.getClass().getMethod("expandSettingsPanel"); + expandSettingsPanelMethodNewVersion = false; + } + } + return expandSettingsPanelMethod; + } + + private Method getCollapsePanelsMethod() throws NoSuchMethodException { + if (collapsePanelsMethod == null) { + collapsePanelsMethod = manager.getClass().getMethod("collapsePanels"); + } + return collapsePanelsMethod; + } + + public void expandNotificationsPanel() { + try { + Method method = getExpandNotificationsPanelMethod(); + if (expandNotificationPanelMethodCustomVersion) { + method.invoke(manager, 0); + } else { + method.invoke(manager); + } + } catch (ReflectiveOperationException e) { + Ln.e("Could not invoke method", e); + } + } + + public void expandSettingsPanel() { + try { + Method method = getExpandSettingsPanel(); + if (expandSettingsPanelMethodNewVersion) { + // new version + method.invoke(manager, (Object) null); + } else { + // old version + method.invoke(manager); + } + } catch (ReflectiveOperationException e) { + Ln.e("Could not invoke method", e); + } + } + + public void collapsePanels() { + try { + Method method = getCollapsePanelsMethod(); + method.invoke(manager); + } catch (ReflectiveOperationException e) { + Ln.e("Could not invoke method", e); + } + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/wrappers/SurfaceControl.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/wrappers/SurfaceControl.java new file mode 100644 index 0000000..3bae4a3 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/wrappers/SurfaceControl.java @@ -0,0 +1,191 @@ +package com.genymobile.scrcpy.wrappers; + +import com.genymobile.scrcpy.AndroidVersions; +import com.genymobile.scrcpy.util.Ln; + +import android.annotation.SuppressLint; +import android.graphics.Rect; +import android.os.Build; +import android.os.IBinder; +import android.view.Surface; + +import java.lang.reflect.Method; + +@SuppressLint("PrivateApi") +public final class SurfaceControl { + + private static final Class CLASS; + + // see + public static final int POWER_MODE_OFF = 0; + public static final int POWER_MODE_NORMAL = 2; + + static { + try { + CLASS = Class.forName("android.view.SurfaceControl"); + } catch (ClassNotFoundException e) { + throw new AssertionError(e); + } + } + + private static Method getBuiltInDisplayMethod; + private static Method setDisplayPowerModeMethod; + private static Method getPhysicalDisplayTokenMethod; + private static Method getPhysicalDisplayIdsMethod; + + private SurfaceControl() { + // only static methods + } + + public static void openTransaction() { + try { + CLASS.getMethod("openTransaction").invoke(null); + } catch (Exception e) { + throw new AssertionError(e); + } + } + + public static void closeTransaction() { + try { + CLASS.getMethod("closeTransaction").invoke(null); + } catch (Exception e) { + throw new AssertionError(e); + } + } + + public static void setDisplayProjection(IBinder displayToken, int orientation, Rect layerStackRect, Rect displayRect) { + try { + CLASS.getMethod("setDisplayProjection", IBinder.class, int.class, Rect.class, Rect.class) + .invoke(null, displayToken, orientation, layerStackRect, displayRect); + } catch (Exception e) { + throw new AssertionError(e); + } + } + + public static void setDisplayLayerStack(IBinder displayToken, int layerStack) { + try { + CLASS.getMethod("setDisplayLayerStack", IBinder.class, int.class).invoke(null, displayToken, layerStack); + } catch (Exception e) { + throw new AssertionError(e); + } + } + + public static void setDisplaySurface(IBinder displayToken, Surface surface) { + try { + CLASS.getMethod("setDisplaySurface", IBinder.class, Surface.class).invoke(null, displayToken, surface); + } catch (Exception e) { + throw new AssertionError(e); + } + } + + public static IBinder createDisplay(String name, boolean secure) throws Exception { + return (IBinder) CLASS.getMethod("createDisplay", String.class, boolean.class).invoke(null, name, secure); + } + + private static Method getGetBuiltInDisplayMethod() throws NoSuchMethodException { + if (getBuiltInDisplayMethod == null) { + // the method signature has changed in Android 10 + // + if (Build.VERSION.SDK_INT < AndroidVersions.API_29_ANDROID_10) { + getBuiltInDisplayMethod = CLASS.getMethod("getBuiltInDisplay", int.class); + } else { + getBuiltInDisplayMethod = CLASS.getMethod("getInternalDisplayToken"); + } + } + return getBuiltInDisplayMethod; + } + + public static boolean hasGetBuildInDisplayMethod() { + try { + getGetBuiltInDisplayMethod(); + return true; + } catch (NoSuchMethodException e) { + return false; + } + } + + public static IBinder getBuiltInDisplay() { + try { + Method method = getGetBuiltInDisplayMethod(); + if (Build.VERSION.SDK_INT < AndroidVersions.API_29_ANDROID_10) { + // call getBuiltInDisplay(0) + return (IBinder) method.invoke(null, 0); + } + + // call getInternalDisplayToken() + return (IBinder) method.invoke(null); + } catch (ReflectiveOperationException e) { + Ln.e("Could not invoke method", e); + return null; + } + } + + private static Method getGetPhysicalDisplayTokenMethod() throws NoSuchMethodException { + if (getPhysicalDisplayTokenMethod == null) { + getPhysicalDisplayTokenMethod = CLASS.getMethod("getPhysicalDisplayToken", long.class); + } + return getPhysicalDisplayTokenMethod; + } + + public static IBinder getPhysicalDisplayToken(long physicalDisplayId) { + try { + Method method = getGetPhysicalDisplayTokenMethod(); + return (IBinder) method.invoke(null, physicalDisplayId); + } catch (ReflectiveOperationException e) { + Ln.e("Could not invoke method", e); + return null; + } + } + + private static Method getGetPhysicalDisplayIdsMethod() throws NoSuchMethodException { + if (getPhysicalDisplayIdsMethod == null) { + getPhysicalDisplayIdsMethod = CLASS.getMethod("getPhysicalDisplayIds"); + } + return getPhysicalDisplayIdsMethod; + } + + public static boolean hasGetPhysicalDisplayIdsMethod() { + try { + getGetPhysicalDisplayIdsMethod(); + return true; + } catch (NoSuchMethodException e) { + return false; + } + } + + public static long[] getPhysicalDisplayIds() { + try { + Method method = getGetPhysicalDisplayIdsMethod(); + return (long[]) method.invoke(null); + } catch (ReflectiveOperationException e) { + Ln.e("Could not invoke method", e); + return null; + } + } + + private static Method getSetDisplayPowerModeMethod() throws NoSuchMethodException { + if (setDisplayPowerModeMethod == null) { + setDisplayPowerModeMethod = CLASS.getMethod("setDisplayPowerMode", IBinder.class, int.class); + } + return setDisplayPowerModeMethod; + } + + public static boolean setDisplayPowerMode(IBinder displayToken, int mode) { + try { + Method method = getSetDisplayPowerModeMethod(); + method.invoke(null, displayToken, mode); + return true; + } catch (ReflectiveOperationException e) { + Ln.e("Could not invoke method", e); + return false; + } + } + + public static void destroyDisplay(IBinder displayToken) { + try { + CLASS.getMethod("destroyDisplay", IBinder.class).invoke(null, displayToken); + } catch (Exception e) { + throw new AssertionError(e); + } + } +} diff --git a/server/third_party/scrcpy-java/com/genymobile/scrcpy/wrappers/WindowManager.java b/server/third_party/scrcpy-java/com/genymobile/scrcpy/wrappers/WindowManager.java new file mode 100644 index 0000000..7ba5cc0 --- /dev/null +++ b/server/third_party/scrcpy-java/com/genymobile/scrcpy/wrappers/WindowManager.java @@ -0,0 +1,267 @@ +package com.genymobile.scrcpy.wrappers; + +import com.genymobile.scrcpy.AndroidVersions; +import com.genymobile.scrcpy.util.Ln; + +import android.annotation.TargetApi; +import android.os.Build; +import android.os.IInterface; +import android.view.IDisplayWindowListener; + +import java.lang.reflect.Method; + +public final class WindowManager { + + @SuppressWarnings("checkstyle:LineLength") + // + public static final int DISPLAY_IME_POLICY_LOCAL = 0; + public static final int DISPLAY_IME_POLICY_FALLBACK_DISPLAY = 1; + public static final int DISPLAY_IME_POLICY_HIDE = 2; + + private final IInterface manager; + private Method getRotationMethod; + + private Method freezeDisplayRotationMethod; + private int freezeDisplayRotationMethodVersion; + + private Method isDisplayRotationFrozenMethod; + private int isDisplayRotationFrozenMethodVersion; + + private Method thawDisplayRotationMethod; + private int thawDisplayRotationMethodVersion; + + private Method getDisplayImePolicyMethod; + private Method setDisplayImePolicyMethod; + + static WindowManager create() { + IInterface manager = ServiceManager.getService("window", "android.view.IWindowManager"); + return new WindowManager(manager); + } + + private WindowManager(IInterface manager) { + this.manager = manager; + } + + private Method getGetRotationMethod() throws NoSuchMethodException { + if (getRotationMethod == null) { + Class cls = manager.getClass(); + try { + // method changed since this commit: + // https://android.googlesource.com/platform/frameworks/base/+/8ee7285128c3843401d4c4d0412cd66e86ba49e3%5E%21/#F2 + getRotationMethod = cls.getMethod("getDefaultDisplayRotation"); + } catch (NoSuchMethodException e) { + // old version + getRotationMethod = cls.getMethod("getRotation"); + } + } + return getRotationMethod; + } + + private Method getFreezeDisplayRotationMethod() throws NoSuchMethodException { + if (freezeDisplayRotationMethod == null) { + try { + // Android 15 preview and 14 QPR3 Beta added a String caller parameter for debugging: + // + freezeDisplayRotationMethod = manager.getClass().getMethod("freezeDisplayRotation", int.class, int.class, String.class); + freezeDisplayRotationMethodVersion = 0; + } catch (NoSuchMethodException e) { + try { + // New method added by this commit: + // + freezeDisplayRotationMethod = manager.getClass().getMethod("freezeDisplayRotation", int.class, int.class); + freezeDisplayRotationMethodVersion = 1; + } catch (NoSuchMethodException e1) { + freezeDisplayRotationMethod = manager.getClass().getMethod("freezeRotation", int.class); + freezeDisplayRotationMethodVersion = 2; + } + } + } + return freezeDisplayRotationMethod; + } + + private Method getIsDisplayRotationFrozenMethod() throws NoSuchMethodException { + if (isDisplayRotationFrozenMethod == null) { + try { + // New method added by this commit: + // + isDisplayRotationFrozenMethod = manager.getClass().getMethod("isDisplayRotationFrozen", int.class); + isDisplayRotationFrozenMethodVersion = 0; + } catch (NoSuchMethodException e) { + isDisplayRotationFrozenMethod = manager.getClass().getMethod("isRotationFrozen"); + isDisplayRotationFrozenMethodVersion = 1; + } + } + return isDisplayRotationFrozenMethod; + } + + private Method getThawDisplayRotationMethod() throws NoSuchMethodException { + if (thawDisplayRotationMethod == null) { + try { + // Android 15 preview and 14 QPR3 Beta added a String caller parameter for debugging: + // + thawDisplayRotationMethod = manager.getClass().getMethod("thawDisplayRotation", int.class, String.class); + thawDisplayRotationMethodVersion = 0; + } catch (NoSuchMethodException e) { + try { + // New method added by this commit: + // + thawDisplayRotationMethod = manager.getClass().getMethod("thawDisplayRotation", int.class); + thawDisplayRotationMethodVersion = 1; + } catch (NoSuchMethodException e1) { + thawDisplayRotationMethod = manager.getClass().getMethod("thawRotation"); + thawDisplayRotationMethodVersion = 2; + } + } + } + return thawDisplayRotationMethod; + } + + public int getRotation() { + try { + Method method = getGetRotationMethod(); + return (int) method.invoke(manager); + } catch (ReflectiveOperationException e) { + Ln.e("Could not invoke method", e); + return 0; + } + } + + public void freezeRotation(int displayId, int rotation) { + try { + Method method = getFreezeDisplayRotationMethod(); + switch (freezeDisplayRotationMethodVersion) { + case 0: + method.invoke(manager, displayId, rotation, "scrcpy#freezeRotation"); + break; + case 1: + method.invoke(manager, displayId, rotation); + break; + default: + if (displayId != 0) { + Ln.e("Secondary display rotation not supported on this device"); + return; + } + method.invoke(manager, rotation); + break; + } + } catch (ReflectiveOperationException e) { + Ln.e("Could not invoke method", e); + } + } + + public boolean isRotationFrozen(int displayId) { + try { + Method method = getIsDisplayRotationFrozenMethod(); + switch (isDisplayRotationFrozenMethodVersion) { + case 0: + return (boolean) method.invoke(manager, displayId); + default: + if (displayId != 0) { + Ln.e("Secondary display rotation not supported on this device"); + return false; + } + return (boolean) method.invoke(manager); + } + } catch (ReflectiveOperationException e) { + Ln.e("Could not invoke method", e); + return false; + } + } + + public void thawRotation(int displayId) { + try { + Method method = getThawDisplayRotationMethod(); + switch (thawDisplayRotationMethodVersion) { + case 0: + method.invoke(manager, displayId, "scrcpy#thawRotation"); + break; + case 1: + method.invoke(manager, displayId); + break; + default: + if (displayId != 0) { + Ln.e("Secondary display rotation not supported on this device"); + return; + } + method.invoke(manager); + break; + } + } catch (ReflectiveOperationException e) { + Ln.e("Could not invoke method", e); + } + } + + @TargetApi(AndroidVersions.API_30_ANDROID_11) + public int[] registerDisplayWindowListener(IDisplayWindowListener listener) { + try { + return (int[]) manager.getClass().getMethod("registerDisplayWindowListener", IDisplayWindowListener.class).invoke(manager, listener); + } catch (Exception e) { + Ln.e("Could not register display window listener", e); + } + return null; + } + + @TargetApi(AndroidVersions.API_30_ANDROID_11) + public void unregisterDisplayWindowListener(IDisplayWindowListener listener) { + try { + manager.getClass().getMethod("unregisterDisplayWindowListener", IDisplayWindowListener.class).invoke(manager, listener); + } catch (Exception e) { + Ln.e("Could not unregister display window listener", e); + } + } + + @TargetApi(AndroidVersions.API_29_ANDROID_10) + private Method getGetDisplayImePolicyMethod() throws NoSuchMethodException { + if (getDisplayImePolicyMethod == null) { + if (Build.VERSION.SDK_INT >= AndroidVersions.API_31_ANDROID_12) { + getDisplayImePolicyMethod = manager.getClass().getMethod("getDisplayImePolicy", int.class); + } else { + getDisplayImePolicyMethod = manager.getClass().getMethod("shouldShowIme", int.class); + } + } + return getDisplayImePolicyMethod; + } + + @TargetApi(AndroidVersions.API_29_ANDROID_10) + public int getDisplayImePolicy(int displayId) { + try { + Method method = getGetDisplayImePolicyMethod(); + if (Build.VERSION.SDK_INT >= AndroidVersions.API_31_ANDROID_12) { + return (int) method.invoke(manager, displayId); + } + boolean shouldShowIme = (boolean) method.invoke(manager, displayId); + return shouldShowIme ? DISPLAY_IME_POLICY_LOCAL : DISPLAY_IME_POLICY_FALLBACK_DISPLAY; + } catch (ReflectiveOperationException e) { + Ln.e("Could not invoke method", e); + return -1; + } + } + + @TargetApi(AndroidVersions.API_29_ANDROID_10) + private Method getSetDisplayImePolicyMethod() throws NoSuchMethodException { + if (setDisplayImePolicyMethod == null) { + if (Build.VERSION.SDK_INT >= AndroidVersions.API_31_ANDROID_12) { + setDisplayImePolicyMethod = manager.getClass().getMethod("setDisplayImePolicy", int.class, int.class); + } else { + setDisplayImePolicyMethod = manager.getClass().getMethod("setShouldShowIme", int.class, boolean.class); + } + } + return setDisplayImePolicyMethod; + } + + @TargetApi(AndroidVersions.API_29_ANDROID_10) + public void setDisplayImePolicy(int displayId, int displayImePolicy) { + try { + Method method = getSetDisplayImePolicyMethod(); + if (Build.VERSION.SDK_INT >= AndroidVersions.API_31_ANDROID_12) { + method.invoke(manager, displayId, displayImePolicy); + } else if (displayImePolicy != DISPLAY_IME_POLICY_HIDE) { + method.invoke(manager, displayId, displayImePolicy == DISPLAY_IME_POLICY_LOCAL); + } else { + Ln.w("DISPLAY_IME_POLICY_HIDE is not supported before Android 12"); + } + } catch (ReflectiveOperationException e) { + Ln.e("Could not invoke method", e); + } + } +} From de3c97afda1bf1c3235d5b6c79146d52a0c08240 Mon Sep 17 00:00:00 2001 From: beyondlevi Date: Wed, 7 Oct 2026 10:28:28 -0300 Subject: [PATCH 02/11] tools: device test client Co-Authored-By: Volund OS <287815646+volundos[bot]@users.noreply.github.com> --- tools/devtest.py | 81 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 tools/devtest.py diff --git a/tools/devtest.py b/tools/devtest.py new file mode 100644 index 0000000..78ee2ab --- /dev/null +++ b/tools/devtest.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 +"""Drives droidbridge-server on a connected device without the Mac app. +Usage: devtest.py tests: enter, edge, clipboard""" +import socket, struct, subprocess, sys, time + +ADB = "adb" +REMOTE = "/data/local/tmp/droidbridge-server.jar" +VERSION = "0.1.0" +LEFT, RIGHT, TOP, BOTTOM = 0, 1, 2, 3 + +def adb(*a): + return subprocess.run([ADB, *a], capture_output=True, text=True).stdout + +class Server: + def __init__(self, jar): + adb("push", jar, REMOTE) + port = int(adb("forward", "tcp:0", "localabstract:droidbridge").strip()) + self.port = port + self.proc = subprocess.Popen([ADB, "shell", f"CLASSPATH={REMOTE}", "app_process", "/", "dev.droidbridge.Main", VERSION], + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True) + line = self.proc.stdout.readline() + while line and "READY" not in line: + print("server:", line.strip()); line = self.proc.stdout.readline() + self.s = socket.create_connection(("127.0.0.1", port)) + self.send(0x01, struct.pack(">H", 1)) + t, p = self.recv() + assert t == 0x81, t + v, w, h = struct.unpack(">HHH", p[:6]) + self.size = (w, h) + print("device:", p[6:].decode(), w, h) + + def send(self, t, p=b""): self.s.sendall(struct.pack(">BI", t, len(p)) + p) + def recv(self, timeout=None): + self.s.settimeout(timeout) + try: + hdr = self._n(5) + except socket.timeout: + return None, None + t, n = struct.unpack(">BI", hdr) + return t, self._n(n) + def _n(self, n): + b = b"" + while len(b) < n: + c = self.s.recv(n - len(b)) + if not c: raise EOFError + b += c + return b + def mouse(self, dx, dy, buttons=0, wheel=0): self.send(0x04, struct.pack(">BhhbB", buttons, dx, dy, wheel, 0)) + def enter(self, side, ratio): self.send(0x02, struct.pack(">BH", side, int(ratio * 65535))) + def close(self): + self.s.close(); time.sleep(0.5) + for line in self.proc.stdout.read().splitlines()[-15:]: print("server:", line) + adb("forward", "--remove", f"tcp:{self.port}") + +def main(): + jar, test = sys.argv[1], sys.argv[2] + S = Server(jar) + try: + if test in ("enter", "edge"): + for ratio in (0.25, 0.75): + t0 = time.time(); S.enter(LEFT, ratio); S.send(0x07) + t, _ = S.recv(5); print(f"enter left {ratio}: {time.time()-t0:.2f}s (pong={t==0x84})") + time.sleep(0.5) + if test == "edge": + for _ in range(40): S.mouse(8, 0); time.sleep(0.008) # into the screen + t, p = S.recv(0.3); print("after moving in, unexpected:", t) + t0 = time.time(); got = None + for i in range(400): + S.mouse(-8, 0); time.sleep(0.008) + t, p = S.recv(0.001) + if t == 0x82: + side, r = struct.unpack(">BH", p); got = (side, r / 65535); break + print("edge:", got, f"after {time.time()-t0:.2f}s") + if test == "clipboard": + S.send(0x06, "droidbridge mac->android ção".encode()) + time.sleep(0.5) + print("set; device echo (should be none):", S.recv(1)[0]) + finally: + S.close() + +main() From 96cd92fe43ccf9aee46af43ee4d7f3ba774eef89 Mon Sep 17 00:00:00 2001 From: beyondlevi Date: Wed, 7 Oct 2026 10:28:49 -0300 Subject: [PATCH 03/11] server: probe regex tolerates nested brackets Co-Authored-By: Volund OS <287815646+volundos[bot]@users.noreply.github.com> --- server/src/dev/droidbridge/PointerProbe.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/dev/droidbridge/PointerProbe.java b/server/src/dev/droidbridge/PointerProbe.java index b55202d..39d9f66 100644 --- a/server/src/dev/droidbridge/PointerProbe.java +++ b/server/src/dev/droidbridge/PointerProbe.java @@ -76,7 +76,7 @@ boolean findDevice(String name, long timeoutMs) throws InterruptedException { Sample sample() throws IOException { String dump = dump(); Matcher pointer = Pattern.compile("DeviceId\\(" + deviceId - + "\\):\\[[^\\]]*?hoveringPointers=\\[Pointer\\(id=\\d+, MOUSE\\) at \\(([-\\d.]+), ([-\\d.]+)\\)").matcher(dump); + + "\\):\\[.*?hoveringPointers=\\[Pointer\\(id=\\d+, MOUSE\\) at \\(([-\\d.]+), ([-\\d.]+)\\)").matcher(dump); if (!pointer.find()) { return null; } From fb14bfd2a0c7ec3569dca1f272f31093455bac93 Mon Sep 17 00:00:00 2001 From: beyondlevi Date: Wed, 7 Oct 2026 10:33:03 -0300 Subject: [PATCH 04/11] mac: menu-bar app (event tap, edge crossing, UHID keyboard map, clipboard sync, adb link) Co-Authored-By: Volund OS <287815646+volundos[bot]@users.noreply.github.com> --- mac/Package.swift | 12 + mac/Resources/en.lproj/Localizable.strings | 22 ++ mac/Resources/pt-BR.lproj/Localizable.strings | 22 ++ mac/Sources/DroidBridge/AppDelegate.swift | 197 +++++++++++++++ mac/Sources/DroidBridge/ClipboardSync.swift | 35 +++ mac/Sources/DroidBridge/DeviceLink.swift | 223 ++++++++++++++++ mac/Sources/DroidBridge/InputCapture.swift | 239 ++++++++++++++++++ mac/Sources/DroidBridge/Settings.swift | 32 +++ mac/Sources/DroidBridge/main.swift | 7 + .../DroidBridgeCore/EdgeGeometry.swift | 78 ++++++ mac/Sources/DroidBridgeCore/Keyboard.swift | 118 +++++++++ mac/Sources/DroidBridgeCore/Wire.swift | 101 ++++++++ .../DroidBridgeCoreTests/CoreTests.swift | 121 +++++++++ mac/scripts/build-app.sh | 44 ++++ 14 files changed, 1251 insertions(+) create mode 100644 mac/Package.swift create mode 100644 mac/Resources/en.lproj/Localizable.strings create mode 100644 mac/Resources/pt-BR.lproj/Localizable.strings create mode 100644 mac/Sources/DroidBridge/AppDelegate.swift create mode 100644 mac/Sources/DroidBridge/ClipboardSync.swift create mode 100644 mac/Sources/DroidBridge/DeviceLink.swift create mode 100644 mac/Sources/DroidBridge/InputCapture.swift create mode 100644 mac/Sources/DroidBridge/Settings.swift create mode 100644 mac/Sources/DroidBridge/main.swift create mode 100644 mac/Sources/DroidBridgeCore/EdgeGeometry.swift create mode 100644 mac/Sources/DroidBridgeCore/Keyboard.swift create mode 100644 mac/Sources/DroidBridgeCore/Wire.swift create mode 100644 mac/Tests/DroidBridgeCoreTests/CoreTests.swift create mode 100755 mac/scripts/build-app.sh diff --git a/mac/Package.swift b/mac/Package.swift new file mode 100644 index 0000000..e3c8971 --- /dev/null +++ b/mac/Package.swift @@ -0,0 +1,12 @@ +// swift-tools-version:5.9 +import PackageDescription + +let package = Package( + name: "DroidBridge", + platforms: [.macOS(.v13)], + targets: [ + .target(name: "DroidBridgeCore"), + .executableTarget(name: "DroidBridge", dependencies: ["DroidBridgeCore"]), + .testTarget(name: "DroidBridgeCoreTests", dependencies: ["DroidBridgeCore"]), + ] +) diff --git a/mac/Resources/en.lproj/Localizable.strings b/mac/Resources/en.lproj/Localizable.strings new file mode 100644 index 0000000..cb208c4 --- /dev/null +++ b/mac/Resources/en.lproj/Localizable.strings @@ -0,0 +1,22 @@ +"status.noAdb" = "adb not found: install Android platform-tools"; +"status.waiting" = "Waiting for an Android device (USB debugging)"; +"status.connecting" = "Connecting…"; +"status.connected" = "Connected: %@"; +"status.onDevice" = "Controlling %@"; +"status.failed" = "Could not connect: %@"; +"menu.grantAccessibility" = "Allow Accessibility access…"; +"menu.share" = "Share mouse and keyboard"; +"menu.placement" = "Android device position"; +"placement.right" = "Right of the Mac"; +"placement.left" = "Left of the Mac"; +"placement.above" = "Above the Mac"; +"placement.below" = "Below the Mac"; +"menu.speed" = "Pointer speed on Android"; +"speed.slow" = "Slow"; +"speed.normal" = "Normal"; +"speed.fast" = "Fast"; +"speed.faster" = "Faster"; +"menu.commandAsControl" = "Command acts as Ctrl"; +"menu.invertScroll" = "Invert scrolling on Android"; +"menu.hotkey" = "Back to the Mac: ⌃⌥⌘B"; +"menu.quit" = "Quit DroidBridge"; diff --git a/mac/Resources/pt-BR.lproj/Localizable.strings b/mac/Resources/pt-BR.lproj/Localizable.strings new file mode 100644 index 0000000..93d4fad --- /dev/null +++ b/mac/Resources/pt-BR.lproj/Localizable.strings @@ -0,0 +1,22 @@ +"status.noAdb" = "adb não encontrado: instale o Android platform-tools"; +"status.waiting" = "Aguardando um Android (depuração USB)"; +"status.connecting" = "Conectando…"; +"status.connected" = "Conectado: %@"; +"status.onDevice" = "Controlando %@"; +"status.failed" = "Não foi possível conectar: %@"; +"menu.grantAccessibility" = "Permitir acesso de Acessibilidade…"; +"menu.share" = "Compartilhar mouse e teclado"; +"menu.placement" = "Posição do Android"; +"placement.right" = "À direita do Mac"; +"placement.left" = "À esquerda do Mac"; +"placement.above" = "Acima do Mac"; +"placement.below" = "Abaixo do Mac"; +"menu.speed" = "Velocidade do ponteiro no Android"; +"speed.slow" = "Lenta"; +"speed.normal" = "Normal"; +"speed.fast" = "Rápida"; +"speed.faster" = "Mais rápida"; +"menu.commandAsControl" = "Command funciona como Ctrl"; +"menu.invertScroll" = "Inverter rolagem no Android"; +"menu.hotkey" = "Voltar ao Mac: ⌃⌥⌘B"; +"menu.quit" = "Sair do DroidBridge"; diff --git a/mac/Sources/DroidBridge/AppDelegate.swift b/mac/Sources/DroidBridge/AppDelegate.swift new file mode 100644 index 0000000..80af30a --- /dev/null +++ b/mac/Sources/DroidBridge/AppDelegate.swift @@ -0,0 +1,197 @@ +import AppKit +import ApplicationServices +import DroidBridgeCore +import os + +func L(_ key: String) -> String { NSLocalizedString(key, comment: "") } + +final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate { + private let link = DeviceLink() + private let capture = InputCapture() + private let clipboard = ClipboardSync() + private var statusItem: NSStatusItem! + private var tapTimer: Timer? + private let log = Logger(subsystem: "dev.droidbridge", category: "app") + + func applicationDidFinishLaunching(_ notification: Notification) { + statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) + statusItem.menu = NSMenu() + statusItem.menu?.delegate = self + updateIcon() + + applySettings() + capture.send = { [weak self] in self?.link.send($0) } + capture.canCross = { [weak self] in + guard let self, Settings.enabled, case .connected = self.link.state else { return false } + return true + } + capture.onRemoteChanged = { [weak self] _ in self?.updateIcon() } + clipboard.send = { [weak self] text in + guard let self, case .connected = self.link.state else { return } + self.link.send(Wire.clipboard(text)) + } + link.onState = { [weak self] state in + guard let self else { return } + if case .connected = state { + self.clipboard.pushCurrent() + } else { + self.capture.returnToMac() + } + self.updateIcon() + } + link.onMessage = { [weak self] message in + switch message { + case let .edge(_, ratio): self?.capture.returnToMac(ratio: ratio) + case let .clipboard(text): self?.clipboard.receivedFromDevice(text) + default: break + } + } + + let trusted = AXIsProcessTrustedWithOptions([kAXTrustedCheckOptionPrompt.takeUnretainedValue() as String: true] as CFDictionary) + log.info("accessibility trusted: \(trusted)") + startTapWhenAllowed() + clipboard.start() + link.start() + } + + func applicationWillTerminate(_ notification: Notification) { + capture.returnToMac() + link.stop() + } + + private func startTapWhenAllowed() { + if capture.start() { return } + tapTimer = Timer.scheduledTimer(withTimeInterval: 2, repeats: true) { [weak self] t in + if self?.capture.start() == true { + t.invalidate() + self?.updateIcon() + } + } + } + + private func applySettings() { + capture.options.placement = Settings.placement + capture.options.speed = Settings.speed + capture.options.commandAsControl = Settings.commandAsControl + capture.options.invertScroll = Settings.invertScroll + } + + private func updateIcon() { + let name: String + if capture.isRemote { + name = "iphone.and.arrow.forward" + } else if case .connected = link.state { + name = "keyboard" + } else { + name = "keyboard.badge.ellipsis" + } + statusItem.button?.image = NSImage(systemSymbolName: name, accessibilityDescription: "DroidBridge") + } + + // MARK: - Menu + + func menuNeedsUpdate(_ menu: NSMenu) { + menu.removeAllItems() + menu.addItem(disabled(statusText())) + if !AXIsProcessTrusted() { + menu.addItem(item(L("menu.grantAccessibility"), #selector(openAccessibility))) + } + menu.addItem(.separator()) + + let share = item(L("menu.share"), #selector(toggleShare)) + share.state = Settings.enabled ? .on : .off + menu.addItem(share) + + let placement = NSMenuItem(title: L("menu.placement"), action: nil, keyEquivalent: "") + let placementMenu = NSMenu() + for p in Placement.allCases { + let i = item(L("placement.\(p.rawValue)"), #selector(choosePlacement(_:))) + i.representedObject = p.rawValue + i.state = Settings.placement == p ? .on : .off + placementMenu.addItem(i) + } + placement.submenu = placementMenu + menu.addItem(placement) + + let speed = NSMenuItem(title: L("menu.speed"), action: nil, keyEquivalent: "") + let speedMenu = NSMenu() + for (key, value) in [("speed.slow", 0.5), ("speed.normal", 1.0), ("speed.fast", 1.5), ("speed.faster", 2.0)] { + let i = item(L(key), #selector(chooseSpeed(_:))) + i.representedObject = value + i.state = Settings.speed == value ? .on : .off + speedMenu.addItem(i) + } + speed.submenu = speedMenu + menu.addItem(speed) + + let cmd = item(L("menu.commandAsControl"), #selector(toggleCommand)) + cmd.state = Settings.commandAsControl ? .on : .off + menu.addItem(cmd) + let scroll = item(L("menu.invertScroll"), #selector(toggleScroll)) + scroll.state = Settings.invertScroll ? .on : .off + menu.addItem(scroll) + + menu.addItem(.separator()) + menu.addItem(disabled(L("menu.hotkey"))) + menu.addItem(.separator()) + menu.addItem(item(L("menu.quit"), #selector(quit))) + } + + private func statusText() -> String { + switch link.state { + case .noAdb: return L("status.noAdb") + case .waiting: return L("status.waiting") + case .connecting: return L("status.connecting") + case let .connected(model, _, _): + return String(format: L(capture.isRemote ? "status.onDevice" : "status.connected"), model) + case let .failed(reason): return String(format: L("status.failed"), reason) + } + } + + private func item(_ title: String, _ action: Selector) -> NSMenuItem { + let i = NSMenuItem(title: title, action: action, keyEquivalent: "") + i.target = self + return i + } + + private func disabled(_ title: String) -> NSMenuItem { + let i = NSMenuItem(title: title, action: nil, keyEquivalent: "") + i.isEnabled = false + return i + } + + @objc private func toggleShare() { + Settings.enabled.toggle() + if !Settings.enabled { capture.returnToMac() } + } + + @objc private func choosePlacement(_ sender: NSMenuItem) { + guard let raw = sender.representedObject as? String, let p = Placement(rawValue: raw) else { return } + Settings.placement = p + applySettings() + } + + @objc private func chooseSpeed(_ sender: NSMenuItem) { + guard let v = sender.representedObject as? Double else { return } + Settings.speed = v + applySettings() + } + + @objc private func toggleCommand() { + Settings.commandAsControl.toggle() + applySettings() + } + + @objc private func toggleScroll() { + Settings.invertScroll.toggle() + applySettings() + } + + @objc private func openAccessibility() { + NSWorkspace.shared.open(URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility")!) + } + + @objc private func quit() { + NSApp.terminate(nil) + } +} diff --git a/mac/Sources/DroidBridge/ClipboardSync.swift b/mac/Sources/DroidBridge/ClipboardSync.swift new file mode 100644 index 0000000..44ba457 --- /dev/null +++ b/mac/Sources/DroidBridge/ClipboardSync.swift @@ -0,0 +1,35 @@ +import AppKit + +/// Keeps the text clipboard the same on both sides. +final class ClipboardSync { + var send: ((String) -> Void)? + private var timer: Timer? + private var lastChangeCount = NSPasteboard.general.changeCount + private var lastFromDevice: String? + + func start() { + timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { [weak self] _ in self?.poll() } + } + + /// Sends the current Mac clipboard (on connect). + func pushCurrent() { + lastChangeCount = NSPasteboard.general.changeCount + if let text = NSPasteboard.general.string(forType: .string), text != lastFromDevice { send?(text) } + } + + func receivedFromDevice(_ text: String) { + lastFromDevice = text + let pb = NSPasteboard.general + pb.clearContents() + pb.setString(text, forType: .string) + lastChangeCount = pb.changeCount + } + + private func poll() { + let pb = NSPasteboard.general + guard pb.changeCount != lastChangeCount else { return } + lastChangeCount = pb.changeCount + guard let text = pb.string(forType: .string), text != lastFromDevice else { return } + send?(text) + } +} diff --git a/mac/Sources/DroidBridge/DeviceLink.swift b/mac/Sources/DroidBridge/DeviceLink.swift new file mode 100644 index 0000000..ac33b05 --- /dev/null +++ b/mac/Sources/DroidBridge/DeviceLink.swift @@ -0,0 +1,223 @@ +import DroidBridgeCore +import Foundation +import os + +/// Finds an Android device over adb, starts the server on it and keeps one connection open. +/// All callbacks run on the main queue. +final class DeviceLink { + enum State: Equatable { + case noAdb + case waiting + case connecting(String) + case connected(model: String, width: Int, height: Int) + case failed(String) + } + + static let serverVersion = "0.1.0" + private static let remoteJar = "/data/local/tmp/droidbridge-server.jar" + private let log = Logger(subsystem: "dev.droidbridge", category: "link") + + var onState: ((State) -> Void)? + var onMessage: ((Wire.Message) -> Void)? + + private(set) var state: State = .waiting { + didSet { if state != oldValue { let s = state; DispatchQueue.main.async { self.onState?(s) } } } + } + + private let queue = DispatchQueue(label: "dev.droidbridge.link") + private let writeQueue = DispatchQueue(label: "dev.droidbridge.write") + private var adb: String? + private var server: Process? + private var socket: Int32 = -1 + private var forwardPort: Int? + private var serial: String? + private var stopped = false + + func start() { + queue.async { self.loop() } + } + + func stop() { + stopped = true + queue.async { self.disconnect() } + } + + func send(_ data: Data) { + writeQueue.async { [weak self] in + guard let self, self.socket >= 0 else { return } + data.withUnsafeBytes { raw in + var offset = 0 + while offset < raw.count { + let n = Darwin.send(self.socket, raw.baseAddress! + offset, raw.count - offset, 0) + if n <= 0 { return } + offset += n + } + } + } + } + + // MARK: - Connection loop (runs on `queue`) + + private func loop() { + while !stopped { + if adb == nil { adb = Self.findAdb() } + guard let adb else { + state = .noAdb + Thread.sleep(forTimeInterval: 3) + continue + } + guard let device = firstDevice(adb) else { + state = .waiting + Thread.sleep(forTimeInterval: 2) + continue + } + do { + try connect(adb: adb, serial: device) + readUntilClosed() + } catch { + log.error("connect failed: \(error.localizedDescription, privacy: .public)") + state = .failed(error.localizedDescription) + } + disconnect() + Thread.sleep(forTimeInterval: 2) + } + } + + private func connect(adb: String, serial: String) throws { + self.serial = serial + state = .connecting(serial) + guard let jar = Bundle.main.url(forResource: "droidbridge-server", withExtension: "jar") else { + throw LinkError("droidbridge-server.jar is missing from the app") + } + try run(adb, ["-s", serial, "push", jar.path, Self.remoteJar]) + let port = try run(adb, ["-s", serial, "forward", "tcp:0", "localabstract:droidbridge"]) + .trimmingCharacters(in: .whitespacesAndNewlines) + guard let p = Int(port) else { throw LinkError("adb forward returned \(port)") } + forwardPort = p + + let process = Process() + process.executableURL = URL(fileURLWithPath: adb) + process.arguments = ["-s", serial, "shell", "CLASSPATH=\(Self.remoteJar)", "app_process", "/", + "dev.droidbridge.Main", Self.serverVersion] + let pipe = Pipe() + process.standardOutput = pipe + process.standardError = pipe + let ready = DispatchSemaphore(value: 0) + var buffer = "" + pipe.fileHandleForReading.readabilityHandler = { [weak self] h in + let chunk = String(decoding: h.availableData, as: UTF8.self) + if chunk.isEmpty { h.readabilityHandler = nil; ready.signal(); return } + buffer += chunk + while let nl = buffer.firstIndex(of: "\n") { + let line = String(buffer[..= 0 { close(socket); socket = -1 } + } + if let server, server.isRunning { server.terminate() } + server = nil + if let adb, let serial, let forwardPort { + _ = try? run(adb, ["-s", serial, "forward", "--remove", "tcp:\(forwardPort)"]) + } + forwardPort = nil + if !stopped, state != .noAdb { state = .waiting } + } + + // MARK: - Helpers + + private func firstDevice(_ adb: String) -> String? { + guard let out = try? run(adb, ["devices"]) else { return nil } + for line in out.split(separator: "\n").dropFirst() { + let parts = line.split(whereSeparator: { $0 == "\t" || $0 == " " }) + if parts.count >= 2, parts[1] == "device" { return String(parts[0]) } + } + return nil + } + + @discardableResult + private func run(_ tool: String, _ args: [String]) throws -> String { + let p = Process() + p.executableURL = URL(fileURLWithPath: tool) + p.arguments = args + let out = Pipe() + p.standardOutput = out + p.standardError = out + try p.run() + let data = out.fileHandleForReading.readDataToEndOfFile() + p.waitUntilExit() + let text = String(decoding: data, as: UTF8.self) + guard p.terminationStatus == 0 else { throw LinkError("adb \(args.joined(separator: " ")): \(text)") } + return text + } + + static func findAdb() -> String? { + var candidates = ["/opt/homebrew/bin/adb", "/usr/local/bin/adb", + NSHomeDirectory() + "/Library/Android/sdk/platform-tools/adb"] + if let sdk = ProcessInfo.processInfo.environment["ANDROID_HOME"] { + candidates.insert(sdk + "/platform-tools/adb", at: 0) + } + if let bundled = Bundle.main.url(forResource: "adb", withExtension: nil)?.path { + candidates.insert(bundled, at: 0) + } + return candidates.first { FileManager.default.isExecutableFile(atPath: $0) } + } + + private static func connectLocal(port: Int) throws -> Int32 { + let fd = Darwin.socket(AF_INET, SOCK_STREAM, 0) + guard fd >= 0 else { throw LinkError("socket() failed") } + var one: Int32 = 1 + setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, socklen_t(MemoryLayout.size)) + setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &one, socklen_t(MemoryLayout.size)) + var addr = sockaddr_in() + addr.sin_family = sa_family_t(AF_INET) + addr.sin_port = in_port_t(UInt16(port).bigEndian) + addr.sin_addr.s_addr = inet_addr("127.0.0.1") + let r = withUnsafePointer(to: &addr) { + $0.withMemoryRebound(to: sockaddr.self, capacity: 1) { Darwin.connect(fd, $0, socklen_t(MemoryLayout.size)) } + } + guard r == 0 else { close(fd); throw LinkError("cannot connect to the device") } + return fd + } +} + +struct LinkError: LocalizedError { + let message: String + init(_ message: String) { self.message = message } + var errorDescription: String? { message } +} diff --git a/mac/Sources/DroidBridge/InputCapture.swift b/mac/Sources/DroidBridge/InputCapture.swift new file mode 100644 index 0000000..ab75039 --- /dev/null +++ b/mac/Sources/DroidBridge/InputCapture.swift @@ -0,0 +1,239 @@ +import AppKit +import CoreGraphics +import DroidBridgeCore +import os + +/// Watches global mouse and keyboard events. While the pointer is "on Android", it swallows them and +/// forwards them to the device instead. +final class InputCapture { + struct Options { + var placement: Placement = .right + var speed: Double = 1.0 + var commandAsControl = true + var invertScroll = false + } + + var options = Options() + /// Sends a frame to the device. + var send: ((Data) -> Void)? + /// Called when control moves to the device (true) or back to the Mac (false). + var onRemoteChanged: ((Bool) -> Void)? + /// Whether crossing to the device is allowed (a device is connected and sharing is on). + var canCross: () -> Bool = { false } + + private(set) var isRemote = false + private let log = Logger(subsystem: "dev.droidbridge", category: "input") + private var tap: CFMachPort? + private var source: CFRunLoopSource? + private var crossedDisplay: CGRect = .zero + /// After coming back, the cursor must move away from the edge before it can cross again. + private var armed = true + private var buttons: UInt8 = 0 + private var keyboard = KeyboardState() + private var scrollAccumulator = CGVector.zero + private let cursor = CursorVisibility() + + /// Starts the event tap. Fails until the app has the Accessibility permission. + @discardableResult + func start() -> Bool { + guard tap == nil else { return true } + let types: [CGEventType] = [.mouseMoved, .leftMouseDown, .leftMouseUp, .leftMouseDragged, .rightMouseDown, + .rightMouseUp, .rightMouseDragged, .otherMouseDown, .otherMouseUp, + .otherMouseDragged, .scrollWheel, .keyDown, .keyUp, .flagsChanged] + let mask = types.reduce(CGEventMask(0)) { $0 | (CGEventMask(1) << $1.rawValue) } + let me = Unmanaged.passUnretained(self).toOpaque() + guard let tap = CGEvent.tapCreate(tap: .cgSessionEventTap, place: .headInsertEventTap, options: .defaultTap, + eventsOfInterest: mask, callback: { _, type, event, info in + let capture = Unmanaged.fromOpaque(info!).takeUnretainedValue() + return capture.handle(type, event) + }, userInfo: me) else { + log.error("event tap not created (Accessibility permission missing?)") + return false + } + self.tap = tap + source = CFMachPortCreateRunLoopSource(nil, tap, 0) + CFRunLoopAddSource(CFRunLoopGetMain(), source, .commonModes) + CGEvent.tapEnable(tap: tap, enable: true) + log.info("event tap started") + return true + } + + /// Brings control back to the Mac, e.g. when the device disconnects or with the hotkey. + func returnToMac(ratio: Double? = nil) { + guard isRemote else { return } + isRemote = false + send?(Wire.leave()) + keyboard.reset() + buttons = 0 + let r = ratio ?? 0.5 + let p = EdgeGeometry.returnPoint(display: crossedDisplay, ratio: r, placement: options.placement) + CGWarpMouseCursorPosition(p) + CGAssociateMouseAndMouseCursorPosition(1) + cursor.show() + armed = false + log.info("back on the Mac at \(p.x), \(p.y)") + onRemoteChanged?(false) + } + + // MARK: - Event handling + + private func handle(_ type: CGEventType, _ event: CGEvent) -> Unmanaged? { + if type == .tapDisabledByTimeout || type == .tapDisabledByUserInput { + if let tap { CGEvent.tapEnable(tap: tap, enable: true) } + return Unmanaged.passUnretained(event) + } + if isRemote { + forward(type, event) + return nil + } + if type == .mouseMoved { + checkCrossing(event) + } + return Unmanaged.passUnretained(event) + } + + private func checkCrossing(_ event: CGEvent) { + let p = event.location + let delta = CGVector(dx: event.getDoubleValueField(.mouseEventDeltaX), dy: event.getDoubleValueField(.mouseEventDeltaY)) + let displays = Self.displays() + if !armed { + if let d = displays.first(where: { $0.contains(p) }), + EdgeGeometry.isClear(of: d, at: p, placement: options.placement) || d != crossedDisplay { + armed = true + } + return + } + guard canCross(), let c = EdgeGeometry.crossing(at: p, delta: delta, placement: options.placement, displays: displays) else { + return + } + isRemote = true + crossedDisplay = c.display + CGAssociateMouseAndMouseCursorPosition(0) + cursor.hide() + send?(Wire.enter(side: options.placement.androidSide, ratio: c.ratio)) + log.info("to Android, ratio \(c.ratio)") + onRemoteChanged?(true) + } + + private func forward(_ type: CGEventType, _ event: CGEvent) { + switch type { + case .mouseMoved, .leftMouseDragged, .rightMouseDragged, .otherMouseDragged: + let dx = Int((event.getDoubleValueField(.mouseEventDeltaX) * options.speed).rounded()) + let dy = Int((event.getDoubleValueField(.mouseEventDeltaY) * options.speed).rounded()) + if dx != 0 || dy != 0 { send?(Wire.mouse(buttons: buttons, dx: dx, dy: dy)) } + case .leftMouseDown, .leftMouseUp, .rightMouseDown, .rightMouseUp, .otherMouseDown, .otherMouseUp: + let number = event.getIntegerValueField(.mouseEventButtonNumber) + // HID bits: 0 left, 1 right, 2 middle, 3 back, 4 forward. macOS numbers: 0 left, 1 right, 2 middle, 3 back, 4 forward. + guard number >= 0, number < 5 else { return } + let bit = UInt8(1) << UInt8(number) + let down = type == .leftMouseDown || type == .rightMouseDown || type == .otherMouseDown + buttons = down ? buttons | bit : buttons & ~bit + send?(Wire.mouse(buttons: buttons, dx: 0, dy: 0)) + case .scrollWheel: + scroll(event) + case .keyDown, .keyUp: + key(type, event) + case .flagsChanged: + flags(event) + default: + break + } + } + + private func scroll(_ event: CGEvent) { + let sign: Double = options.invertScroll ? -1 : 1 + var wheel = 0 + var hwheel = 0 + if event.getIntegerValueField(.scrollWheelEventIsContinuous) != 0 { + // Trackpad / Magic Mouse: pixels. About 24 px per wheel notch. + scrollAccumulator.dy += event.getDoubleValueField(.scrollWheelEventPointDeltaAxis1) * sign + scrollAccumulator.dx += event.getDoubleValueField(.scrollWheelEventPointDeltaAxis2) * sign + wheel = Int(scrollAccumulator.dy / 24) + hwheel = Int(scrollAccumulator.dx / 24) + scrollAccumulator.dy -= Double(wheel) * 24 + scrollAccumulator.dx -= Double(hwheel) * 24 + } else { + wheel = Int(Double(event.getIntegerValueField(.scrollWheelEventDeltaAxis1)) * sign) + hwheel = Int(Double(event.getIntegerValueField(.scrollWheelEventDeltaAxis2)) * sign) + } + // HID AC Pan is positive to the right; macOS axis 2 is positive to the left. + if wheel != 0 || hwheel != 0 { send?(Wire.mouse(buttons: buttons, dx: 0, dy: 0, wheel: wheel, hwheel: -hwheel)) } + } + + private func key(_ type: CGEventType, _ event: CGEvent) { + let code = UInt16(event.getIntegerValueField(.keyboardEventKeycode)) + if type == .keyDown, isReturnHotkey(code, event.flags) { + returnToMac() + return + } + guard let usage = KeyMap.usage(forKeyCode: code) else { return } + var changed = keyboard.setModifiers(HIDModifiers.from(flags: event.flags, commandAsControl: options.commandAsControl)) + if type == .keyDown { + // Android repeats held keys itself; ignore macOS autorepeat. + if event.getIntegerValueField(.keyboardEventAutorepeat) != 0 { return } + changed = keyboard.press(usage) || changed + } else { + changed = keyboard.release(usage) || changed + } + if changed { send?(Wire.keys(keyboard.report)) } + } + + private func flags(_ event: CGEvent) { + let code = UInt16(event.getIntegerValueField(.keyboardEventKeycode)) + if code == KeyMap.capsLockKeyCode { + // Caps Lock arrives as a flag change; tap it on the device. + keyboard.press(KeyMap.capsLockUsage) + send?(Wire.keys(keyboard.report)) + keyboard.release(KeyMap.capsLockUsage) + send?(Wire.keys(keyboard.report)) + return + } + if keyboard.setModifiers(HIDModifiers.from(flags: event.flags, commandAsControl: options.commandAsControl)) { + send?(Wire.keys(keyboard.report)) + } + } + + /// Control + Option + Command + B brings the pointer back to the Mac. + private func isReturnHotkey(_ code: UInt16, _ flags: CGEventFlags) -> Bool { + code == 0x0B && flags.contains([.maskControl, .maskAlternate, .maskCommand]) + } + + static func displays() -> [CGRect] { + var count: UInt32 = 0 + CGGetActiveDisplayList(0, nil, &count) + var ids = [CGDirectDisplayID](repeating: 0, count: Int(count)) + CGGetActiveDisplayList(count, &ids, &count) + return ids.prefix(Int(count)).map { CGDisplayBounds($0) } + } +} + +/// Hides the cursor while the pointer is on the device. A background app can only hide the cursor +/// after setting the window server property "SetsCursorInBackground" (the approach Synergy/Barrier use). +final class CursorVisibility { + private var hidden = false + private var backgroundEnabled = false + + func hide() { + guard !hidden else { return } + enableBackgroundCursorControl() + CGDisplayHideCursor(CGMainDisplayID()) + hidden = true + } + + func show() { + guard hidden else { return } + CGDisplayShowCursor(CGMainDisplayID()) + hidden = false + } + + private func enableBackgroundCursorControl() { + guard !backgroundEnabled else { return } + backgroundEnabled = true + typealias DefaultConnection = @convention(c) () -> Int32 + typealias SetProperty = @convention(c) (Int32, Int32, CFString, CFTypeRef) -> Int32 + let handle = dlopen(nil, RTLD_NOW) + guard let c = dlsym(handle, "_CGSDefaultConnection"), let s = dlsym(handle, "CGSSetConnectionProperty") else { return } + let connection = unsafeBitCast(c, to: DefaultConnection.self)() + _ = unsafeBitCast(s, to: SetProperty.self)(connection, connection, "SetsCursorInBackground" as CFString, kCFBooleanTrue) + } +} diff --git a/mac/Sources/DroidBridge/Settings.swift b/mac/Sources/DroidBridge/Settings.swift new file mode 100644 index 0000000..4be234f --- /dev/null +++ b/mac/Sources/DroidBridge/Settings.swift @@ -0,0 +1,32 @@ +import DroidBridgeCore +import Foundation + +/// User preferences, stored in UserDefaults. +struct Settings { + private static let d = UserDefaults.standard + + static var enabled: Bool { + get { d.object(forKey: "enabled") as? Bool ?? true } + set { d.set(newValue, forKey: "enabled") } + } + + static var placement: Placement { + get { Placement(rawValue: d.string(forKey: "placement") ?? "") ?? .right } + set { d.set(newValue.rawValue, forKey: "placement") } + } + + static var speed: Double { + get { d.object(forKey: "speed") as? Double ?? 1.0 } + set { d.set(newValue, forKey: "speed") } + } + + static var commandAsControl: Bool { + get { d.object(forKey: "commandAsControl") as? Bool ?? true } + set { d.set(newValue, forKey: "commandAsControl") } + } + + static var invertScroll: Bool { + get { d.object(forKey: "invertScroll") as? Bool ?? false } + set { d.set(newValue, forKey: "invertScroll") } + } +} diff --git a/mac/Sources/DroidBridge/main.swift b/mac/Sources/DroidBridge/main.swift new file mode 100644 index 0000000..c39deb8 --- /dev/null +++ b/mac/Sources/DroidBridge/main.swift @@ -0,0 +1,7 @@ +import AppKit + +let app = NSApplication.shared +let delegate = AppDelegate() +app.delegate = delegate +app.setActivationPolicy(.accessory) +app.run() diff --git a/mac/Sources/DroidBridgeCore/EdgeGeometry.swift b/mac/Sources/DroidBridgeCore/EdgeGeometry.swift new file mode 100644 index 0000000..6a33d81 --- /dev/null +++ b/mac/Sources/DroidBridgeCore/EdgeGeometry.swift @@ -0,0 +1,78 @@ +import CoreGraphics + +/// Where the Android device sits relative to the Mac's screens. +public enum Placement: String, CaseIterable { + case right, left, above, below + + /// The Android screen edge the pointer enters through (and leaves through). + public var androidSide: Side { + switch self { + case .right: return .left + case .left: return .right + case .above: return .bottom + case .below: return .top + } + } +} + +public struct Crossing: Equatable { + public let display: CGRect + /// Position along the edge, 0 at the top or left end, 1 at the other. + public let ratio: Double +} + +/// Screen-edge math in global display coordinates (origin at the top left of the main display, y down). +public enum EdgeGeometry { + /// Distance from the edge (px) that counts as touching it: the cursor stops at maxX - 1. + static let slop: CGFloat = 1.5 + + /// Returns the crossing when the cursor is pushed out of a free edge of the screens toward the device. + public static func crossing(at p: CGPoint, delta: CGVector, placement: Placement, displays: [CGRect]) -> Crossing? { + for d in displays { + switch placement { + case .right: + guard delta.dx > 0, p.x >= d.maxX - slop, p.x < d.maxX + slop, p.y >= d.minY, p.y < d.maxY, + !covered(CGPoint(x: d.maxX + 1, y: p.y), displays) else { continue } + return Crossing(display: d, ratio: Double((p.y - d.minY) / d.height)) + case .left: + guard delta.dx < 0, p.x <= d.minX + slop - 1, p.x >= d.minX - slop, p.y >= d.minY, p.y < d.maxY, + !covered(CGPoint(x: d.minX - 1, y: p.y), displays) else { continue } + return Crossing(display: d, ratio: Double((p.y - d.minY) / d.height)) + case .above: + guard delta.dy < 0, p.y <= d.minY + slop - 1, p.y >= d.minY - slop, p.x >= d.minX, p.x < d.maxX, + !covered(CGPoint(x: p.x, y: d.minY - 1), displays) else { continue } + return Crossing(display: d, ratio: Double((p.x - d.minX) / d.width)) + case .below: + guard delta.dy > 0, p.y >= d.maxY - slop, p.y < d.maxY + slop, p.x >= d.minX, p.x < d.maxX, + !covered(CGPoint(x: p.x, y: d.maxY + 1), displays) else { continue } + return Crossing(display: d, ratio: Double((p.x - d.minX) / d.width)) + } + } + return nil + } + + /// Where to put the Mac cursor when the pointer comes back: just inside the edge it left through. + public static func returnPoint(display d: CGRect, ratio: Double, placement: Placement, inset: CGFloat = 3) -> CGPoint { + let r = CGFloat(min(max(ratio, 0), 1)) + switch placement { + case .right: return CGPoint(x: d.maxX - inset, y: min(d.minY + r * d.height, d.maxY - 1)) + case .left: return CGPoint(x: d.minX + inset - 1, y: min(d.minY + r * d.height, d.maxY - 1)) + case .above: return CGPoint(x: min(d.minX + r * d.width, d.maxX - 1), y: d.minY + inset - 1) + case .below: return CGPoint(x: min(d.minX + r * d.width, d.maxX - 1), y: d.maxY - inset) + } + } + + /// Whether the cursor has moved far enough from the edge to cross again (avoids bouncing back). + public static func isClear(of d: CGRect, at p: CGPoint, placement: Placement, distance: CGFloat = 12) -> Bool { + switch placement { + case .right: return p.x < d.maxX - distance + case .left: return p.x > d.minX + distance + case .above: return p.y > d.minY + distance + case .below: return p.y < d.maxY - distance + } + } + + static func covered(_ p: CGPoint, _ displays: [CGRect]) -> Bool { + displays.contains { $0.contains(p) } + } +} diff --git a/mac/Sources/DroidBridgeCore/Keyboard.swift b/mac/Sources/DroidBridgeCore/Keyboard.swift new file mode 100644 index 0000000..c0f6efd --- /dev/null +++ b/mac/Sources/DroidBridgeCore/Keyboard.swift @@ -0,0 +1,118 @@ +import CoreGraphics + +/// macOS virtual key codes (Carbon kVK_*) to USB HID keyboard usages. +public enum KeyMap { + public static let capsLockKeyCode: UInt16 = 0x39 + public static let capsLockUsage: UInt8 = 0x39 + + static let table: [UInt16: UInt8] = [ + 0x00: 0x04, 0x0B: 0x05, 0x08: 0x06, 0x02: 0x07, 0x0E: 0x08, 0x03: 0x09, 0x05: 0x0A, 0x04: 0x0B, // a b c d e f g h + 0x22: 0x0C, 0x26: 0x0D, 0x28: 0x0E, 0x25: 0x0F, 0x2E: 0x10, 0x2D: 0x11, 0x1F: 0x12, 0x23: 0x13, // i j k l m n o p + 0x0C: 0x14, 0x0F: 0x15, 0x01: 0x16, 0x11: 0x17, 0x20: 0x18, 0x09: 0x19, 0x0D: 0x1A, 0x07: 0x1B, // q r s t u v w x + 0x10: 0x1C, 0x06: 0x1D, // y z + 0x12: 0x1E, 0x13: 0x1F, 0x14: 0x20, 0x15: 0x21, 0x17: 0x22, 0x16: 0x23, 0x1A: 0x24, 0x1C: 0x25, // 1-8 + 0x19: 0x26, 0x1D: 0x27, // 9 0 + 0x24: 0x28, 0x35: 0x29, 0x33: 0x2A, 0x30: 0x2B, 0x31: 0x2C, // return esc backspace tab space + 0x1B: 0x2D, 0x18: 0x2E, 0x21: 0x2F, 0x1E: 0x30, 0x2A: 0x31, // - = [ ] backslash + 0x29: 0x33, 0x27: 0x34, 0x32: 0x35, 0x2B: 0x36, 0x2F: 0x37, 0x2C: 0x38, // ; ' ` , . / + 0x0A: 0x64, // ISO section (non-US \ |) + 0x7A: 0x3A, 0x78: 0x3B, 0x63: 0x3C, 0x76: 0x3D, 0x60: 0x3E, 0x61: 0x3F, // F1-F6 + 0x62: 0x40, 0x64: 0x41, 0x65: 0x42, 0x6D: 0x43, 0x67: 0x44, 0x6F: 0x45, // F7-F12 + 0x72: 0x49, 0x73: 0x4A, 0x74: 0x4B, 0x75: 0x4C, 0x77: 0x4D, 0x79: 0x4E, // help(insert) home pgup del end pgdn + 0x7C: 0x4F, 0x7B: 0x50, 0x7D: 0x51, 0x7E: 0x52, // right left down up + 0x47: 0x53, 0x4B: 0x54, 0x43: 0x55, 0x4E: 0x56, 0x45: 0x57, 0x4C: 0x58, // clear / * - + enter + 0x53: 0x59, 0x54: 0x5A, 0x55: 0x5B, 0x56: 0x5C, 0x57: 0x5D, 0x58: 0x5E, // keypad 1-6 + 0x59: 0x5F, 0x5B: 0x60, 0x5C: 0x61, 0x52: 0x62, 0x41: 0x63, 0x51: 0x67, // keypad 7 8 9 0 . = + ] + + public static func usage(forKeyCode code: UInt16) -> UInt8? { table[code] } +} + +/// HID modifier byte bits. +public struct HIDModifiers: OptionSet, Equatable { + public let rawValue: UInt8 + public init(rawValue: UInt8) { self.rawValue = rawValue } + + public static let leftControl = HIDModifiers(rawValue: 0x01) + public static let leftShift = HIDModifiers(rawValue: 0x02) + public static let leftAlt = HIDModifiers(rawValue: 0x04) + public static let leftMeta = HIDModifiers(rawValue: 0x08) + public static let rightControl = HIDModifiers(rawValue: 0x10) + public static let rightShift = HIDModifiers(rawValue: 0x20) + public static let rightAlt = HIDModifiers(rawValue: 0x40) + public static let rightMeta = HIDModifiers(rawValue: 0x80) + + // Device-dependent bits of CGEventFlags (IOKit NX_DEVICE*KEYMASK). + static let nxLeftControl: UInt64 = 0x0001 + static let nxLeftShift: UInt64 = 0x0002 + static let nxRightShift: UInt64 = 0x0004 + static let nxLeftCommand: UInt64 = 0x0008 + static let nxRightCommand: UInt64 = 0x0010 + static let nxLeftAlt: UInt64 = 0x0020 + static let nxRightAlt: UInt64 = 0x0040 + static let nxRightControl: UInt64 = 0x2000 + + /// Converts macOS modifier flags. With `commandAsControl`, Command becomes Ctrl (so Cmd+C copies on + /// Android) and Control becomes Meta; otherwise Command is Meta and Control is Ctrl. + public static func from(flags: CGEventFlags, commandAsControl: Bool) -> HIDModifiers { + let raw = flags.rawValue + var m: HIDModifiers = [] + func side(_ generic: CGEventFlags, _ left: UInt64, _ right: UInt64, _ l: HIDModifiers, _ r: HIDModifiers) { + guard flags.contains(generic) else { return } + let hasLeft = raw & left != 0 + let hasRight = raw & right != 0 + if hasRight { m.insert(r) } + if hasLeft || !hasRight { m.insert(l) } + } + side(.maskShift, nxLeftShift, nxRightShift, .leftShift, .rightShift) + side(.maskAlternate, nxLeftAlt, nxRightAlt, .leftAlt, .rightAlt) + if commandAsControl { + side(.maskCommand, nxLeftCommand, nxRightCommand, .leftControl, .rightControl) + side(.maskControl, nxLeftControl, nxRightControl, .leftMeta, .rightMeta) + } else { + side(.maskCommand, nxLeftCommand, nxRightCommand, .leftMeta, .rightMeta) + side(.maskControl, nxLeftControl, nxRightControl, .leftControl, .rightControl) + } + return m + } +} + +/// Keys held down on the Android keyboard, as an 8-byte HID boot report. +public struct KeyboardState: Equatable { + public private(set) var modifiers: HIDModifiers = [] + public private(set) var pressed: [UInt8] = [] + + public init() {} + + /// Returns true when the report changed. + @discardableResult + public mutating func press(_ usage: UInt8) -> Bool { + guard !pressed.contains(usage) else { return false } + if pressed.count == 6 { pressed.removeFirst() } + pressed.append(usage) + return true + } + + @discardableResult + public mutating func release(_ usage: UInt8) -> Bool { + guard let i = pressed.firstIndex(of: usage) else { return false } + pressed.remove(at: i) + return true + } + + @discardableResult + public mutating func setModifiers(_ m: HIDModifiers) -> Bool { + guard m != modifiers else { return false } + modifiers = m + return true + } + + public mutating func reset() { + modifiers = [] + pressed = [] + } + + public var report: [UInt8] { + [modifiers.rawValue, 0] + pressed + [UInt8](repeating: 0, count: 6 - pressed.count) + } +} diff --git a/mac/Sources/DroidBridgeCore/Wire.swift b/mac/Sources/DroidBridgeCore/Wire.swift new file mode 100644 index 0000000..ebfb8d2 --- /dev/null +++ b/mac/Sources/DroidBridgeCore/Wire.swift @@ -0,0 +1,101 @@ +import Foundation + +/// Wire protocol shared with the Android server (server/src/dev/droidbridge/Protocol.java). +/// Every message is: u8 type, u32 payload length (big-endian), payload. +public enum Wire { + public static let version: UInt16 = 1 + public static let maxPayload = 1 << 20 + + public enum Out: UInt8 { + case hello = 0x01, enter = 0x02, leave = 0x03, mouse = 0x04, keys = 0x05, clipboard = 0x06, ping = 0x07 + } + + public enum In: UInt8 { + case device = 0x81, edge = 0x82, clipboard = 0x83, pong = 0x84 + } + + public enum Message: Equatable { + case device(protocolVersion: UInt16, width: Int, height: Int, model: String) + case edge(side: Side, ratio: Double) + case clipboard(String) + case pong + case unknown(UInt8) + } + + public static func frame(_ type: Out, _ payload: [UInt8] = []) -> Data { + var d = Data([type.rawValue]) + d.append(contentsOf: be32(UInt32(payload.count))) + d.append(contentsOf: payload) + return d + } + + public static func hello() -> Data { frame(.hello, be16(version)) } + + public static func enter(side: Side, ratio: Double) -> Data { + frame(.enter, [side.rawValue] + be16(ratio16(ratio))) + } + + public static func leave() -> Data { frame(.leave) } + + public static func mouse(buttons: UInt8, dx: Int, dy: Int, wheel: Int = 0, hwheel: Int = 0) -> Data { + frame(.mouse, [buttons & 0x1F] + be16(UInt16(bitPattern: Int16(clamping: dx))) + be16(UInt16(bitPattern: Int16(clamping: dy))) + + [UInt8(bitPattern: Int8(clamping: wheel)), UInt8(bitPattern: Int8(clamping: hwheel))]) + } + + public static func keys(_ report: [UInt8]) -> Data { + precondition(report.count == 8) + return frame(.keys, report) + } + + public static func clipboard(_ text: String) -> Data { frame(.clipboard, Array(text.utf8)) } + + public static func ping() -> Data { frame(.ping) } + + public static func decode(type: UInt8, payload: [UInt8]) -> Message { + switch In(rawValue: type) { + case .device where payload.count >= 6: + return .device(protocolVersion: u16(payload, 0), width: Int(u16(payload, 2)), height: Int(u16(payload, 4)), + model: String(decoding: payload[6...], as: UTF8.self)) + case .edge where payload.count >= 3: + return .edge(side: Side(rawValue: payload[0]) ?? .left, ratio: Double(u16(payload, 1)) / 65535) + case .clipboard: + return .clipboard(String(decoding: payload, as: UTF8.self)) + case .pong: + return .pong + default: + return .unknown(type) + } + } + + static func ratio16(_ r: Double) -> UInt16 { UInt16((min(max(r, 0), 1) * 65535).rounded()) } + static func be16(_ v: UInt16) -> [UInt8] { [UInt8(v >> 8), UInt8(v & 0xFF)] } + static func be32(_ v: UInt32) -> [UInt8] { [UInt8(v >> 24), UInt8((v >> 16) & 0xFF), UInt8((v >> 8) & 0xFF), UInt8(v & 0xFF)] } + static func u16(_ b: [UInt8], _ i: Int) -> UInt16 { UInt16(b[i]) << 8 | UInt16(b[i + 1]) } +} + +/// A side of the Android screen. +public enum Side: UInt8 { + case left = 0, right = 1, top = 2, bottom = 3 +} + +/// Splits a byte stream into messages. +public struct FrameReader { + private var buffer: [UInt8] = [] + + public init() {} + + public mutating func feed(_ bytes: [UInt8]) throws -> [Wire.Message] { + buffer.append(contentsOf: bytes) + var out: [Wire.Message] = [] + while buffer.count >= 5 { + let length = Int(buffer[1]) << 24 | Int(buffer[2]) << 16 | Int(buffer[3]) << 8 | Int(buffer[4]) + guard length <= Wire.maxPayload else { throw FrameError.tooLarge(length) } + guard buffer.count >= 5 + length else { break } + out.append(Wire.decode(type: buffer[0], payload: Array(buffer[5..<(5 + length)]))) + buffer.removeFirst(5 + length) + } + return out + } + + public enum FrameError: Error { case tooLarge(Int) } +} diff --git a/mac/Tests/DroidBridgeCoreTests/CoreTests.swift b/mac/Tests/DroidBridgeCoreTests/CoreTests.swift new file mode 100644 index 0000000..12b9a9c --- /dev/null +++ b/mac/Tests/DroidBridgeCoreTests/CoreTests.swift @@ -0,0 +1,121 @@ +import CoreGraphics +import XCTest +@testable import DroidBridgeCore + +final class WireTests: XCTestCase { + func testMouseFrame() { + let d = Wire.mouse(buttons: 1, dx: -2, dy: 300, wheel: -1) + XCTAssertEqual([UInt8](d), [0x04, 0, 0, 0, 7, 1, 0xFF, 0xFE, 0x01, 0x2C, 0xFF, 0]) + } + + func testEnterFrame() { + XCTAssertEqual([UInt8](Wire.enter(side: .left, ratio: 1)), [0x02, 0, 0, 0, 3, 0, 0xFF, 0xFF]) + } + + func testReaderSplitsAndJoins() throws { + var r = FrameReader() + let edge: [UInt8] = [0x82, 0, 0, 0, 3, 1, 0x80, 0x00] + let clip: [UInt8] = [0x83, 0, 0, 0, 2] + Array("oi".utf8) + XCTAssertEqual(try r.feed(Array(edge[0..<4])), []) + let msgs = try r.feed(Array(edge[4...]) + clip) + XCTAssertEqual(msgs.count, 2) + if case let .edge(side, ratio) = msgs[0] { + XCTAssertEqual(side, .right) + XCTAssertEqual(ratio, 32768.0 / 65535, accuracy: 1e-9) + } else { XCTFail() } + XCTAssertEqual(msgs[1], .clipboard("oi")) + } + + func testDeviceMessage() throws { + var r = FrameReader() + let p: [UInt8] = [0, 1, 0x09, 0x90, 0x07, 0x38] + Array("samsung X".utf8) + let msgs = try r.feed([0x81, 0, 0, 0, UInt8(p.count)] + p) + XCTAssertEqual(msgs, [.device(protocolVersion: 1, width: 2448, height: 1848, model: "samsung X")]) + } +} + +final class EdgeTests: XCTestCase { + let main = CGRect(x: 0, y: 0, width: 1512, height: 982) + let external = CGRect(x: 1512, y: -200, width: 1920, height: 1080) + + func testRightEdgeSingleDisplay() { + let c = EdgeGeometry.crossing(at: CGPoint(x: 1511.5, y: 491), delta: CGVector(dx: 3, dy: 0), placement: .right, displays: [main]) + XCTAssertEqual(c?.display, main) + XCTAssertEqual(c?.ratio ?? 0, 0.5, accuracy: 0.001) + } + + func testNoCrossingWhenMovingInward() { + XCTAssertNil(EdgeGeometry.crossing(at: CGPoint(x: 1511.5, y: 491), delta: CGVector(dx: -3, dy: 0), placement: .right, displays: [main])) + } + + func testEdgeShared() { + // The main display's right edge leads to the external one, so it is not free. + XCTAssertNil(EdgeGeometry.crossing(at: CGPoint(x: 1511.5, y: 300), delta: CGVector(dx: 3, dy: 0), placement: .right, displays: [main, external])) + let c = EdgeGeometry.crossing(at: CGPoint(x: 3431.6, y: 340), delta: CGVector(dx: 3, dy: 0), placement: .right, displays: [main, external]) + XCTAssertEqual(c?.display, external) + XCTAssertEqual(c?.ratio ?? 0, 0.5, accuracy: 0.001) + } + + func testPartlyFreeEdge() { + // Below the external display's bottom (y 880..982) the main display's right edge is free. + XCTAssertNotNil(EdgeGeometry.crossing(at: CGPoint(x: 1511.5, y: 950), delta: CGVector(dx: 1, dy: 0), placement: .right, displays: [main, external])) + } + + func testAboveAndBelow() { + XCTAssertNotNil(EdgeGeometry.crossing(at: CGPoint(x: 700, y: 0), delta: CGVector(dx: 0, dy: -2), placement: .above, displays: [main])) + XCTAssertNotNil(EdgeGeometry.crossing(at: CGPoint(x: 700, y: 981.2), delta: CGVector(dx: 0, dy: 2), placement: .below, displays: [main])) + XCTAssertNil(EdgeGeometry.crossing(at: CGPoint(x: 700, y: 500), delta: CGVector(dx: 0, dy: 2), placement: .below, displays: [main])) + } + + func testReturnPointIsInsideAndClear() { + let p = EdgeGeometry.returnPoint(display: main, ratio: 0.25, placement: .right) + XCTAssertTrue(main.contains(p)) + XCTAssertEqual(p.y, 245.5, accuracy: 0.01) + XCTAssertNil(EdgeGeometry.crossing(at: p, delta: CGVector(dx: 1, dy: 0), placement: .right, displays: [main])) + XCTAssertFalse(EdgeGeometry.isClear(of: main, at: p, placement: .right)) + } + + func testPlacementSides() { + XCTAssertEqual(Placement.right.androidSide, .left) + XCTAssertEqual(Placement.below.androidSide, .top) + } +} + +final class KeyboardTests: XCTestCase { + func testLetters() { + XCTAssertEqual(KeyMap.usage(forKeyCode: 0x00), 0x04) // a + XCTAssertEqual(KeyMap.usage(forKeyCode: 0x06), 0x1D) // z + XCTAssertEqual(KeyMap.usage(forKeyCode: 0x24), 0x28) // return + XCTAssertNil(KeyMap.usage(forKeyCode: 0x3F)) // fn + } + + func testTableHasNoDuplicateUsages() { + let usages = KeyMap.table.values + XCTAssertEqual(Set(usages).count, usages.count) + } + + func testCommandAsControl() { + let flags = CGEventFlags(rawValue: CGEventFlags.maskCommand.rawValue | HIDModifiers.nxLeftCommand) + XCTAssertEqual(HIDModifiers.from(flags: flags, commandAsControl: true), .leftControl) + XCTAssertEqual(HIDModifiers.from(flags: flags, commandAsControl: false), .leftMeta) + } + + func testRightShiftAndGenericFlag() { + let right = CGEventFlags(rawValue: CGEventFlags.maskShift.rawValue | HIDModifiers.nxRightShift) + XCTAssertEqual(HIDModifiers.from(flags: right, commandAsControl: true), .rightShift) + XCTAssertEqual(HIDModifiers.from(flags: .maskAlternate, commandAsControl: true), .leftAlt) + } + + func testReport() { + var k = KeyboardState() + k.setModifiers(.leftShift) + k.press(0x04) + k.press(0x05) + XCTAssertEqual(k.report, [0x02, 0, 0x04, 0x05, 0, 0, 0, 0]) + XCTAssertFalse(k.press(0x04)) + k.release(0x04) + XCTAssertEqual(k.report, [0x02, 0, 0x05, 0, 0, 0, 0, 0]) + for u: UInt8 in 10...16 { k.press(u) } + XCTAssertEqual(k.pressed.count, 6) + } +} diff --git a/mac/scripts/build-app.sh b/mac/scripts/build-app.sh new file mode 100755 index 0000000..2ab5d33 --- /dev/null +++ b/mac/scripts/build-app.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# Builds dist/DroidBridge.app: the Swift menu-bar app plus the Android server jar. +# Env: DROIDBRIDGE_VERSION (default 0.1.0), SIGN_IDENTITY (default "-", ad-hoc), SERVER_JAR (prebuilt jar). +set -euo pipefail +cd "$(dirname "$0")/.." +VERSION=${DROIDBRIDGE_VERSION:-0.1.0} +IDENTITY=${SIGN_IDENTITY:--} +APP=dist/DroidBridge.app + +swift build -c release --arch arm64 --arch x86_64 +BIN=$(swift build -c release --arch arm64 --arch x86_64 --show-bin-path)/DroidBridge + +JAR=${SERVER_JAR:-../server/build/droidbridge-server.jar} +if [[ ! -f "$JAR" ]]; then + DROIDBRIDGE_VERSION=$VERSION ../server/build.sh +fi + +rm -rf "$APP" +mkdir -p "$APP/Contents/MacOS" "$APP/Contents/Resources" +cp "$BIN" "$APP/Contents/MacOS/DroidBridge" +cp "$JAR" "$APP/Contents/Resources/droidbridge-server.jar" +cp -R Resources/*.lproj "$APP/Contents/Resources/" +cat > "$APP/Contents/Info.plist" < + + + + CFBundleIdentifierdev.droidbridge.mac + CFBundleNameDroidBridge + CFBundleDisplayNameDroidBridge + CFBundleExecutableDroidBridge + CFBundlePackageTypeAPPL + CFBundleShortVersionString$VERSION + CFBundleVersion$VERSION + CFBundleDevelopmentRegionen + CFBundleLocalizationsenpt-BR + LSMinimumSystemVersion13.0 + LSUIElement + NSHumanReadableCopyrightApache License 2.0 + + +PLIST +codesign --force --sign "$IDENTITY" --identifier dev.droidbridge.mac "$APP" +echo "$APP" From ccefd6c31d18157cb722411c8230457500c81ad9 Mon Sep 17 00:00:00 2001 From: beyondlevi Date: Wed, 7 Oct 2026 10:41:49 -0300 Subject: [PATCH 05/11] mac: hide the cursor by coming to the front, speed 1x-6x (default 3x), local signing identity Co-Authored-By: Volund OS <287815646+volundos[bot]@users.noreply.github.com> --- mac/Resources/en.lproj/Localizable.strings | 5 +-- mac/Resources/pt-BR.lproj/Localizable.strings | 5 +-- mac/Sources/DroidBridge/AppDelegate.swift | 4 +- mac/Sources/DroidBridge/InputCapture.swift | 35 ++++++++++++---- mac/Sources/DroidBridge/Settings.swift | 2 +- mac/scripts/build-app.sh | 5 +++ mac/scripts/create-signing-identity.sh | 40 +++++++++++++++++++ 7 files changed, 78 insertions(+), 18 deletions(-) create mode 100755 mac/scripts/create-signing-identity.sh diff --git a/mac/Resources/en.lproj/Localizable.strings b/mac/Resources/en.lproj/Localizable.strings index cb208c4..8c5b1ca 100644 --- a/mac/Resources/en.lproj/Localizable.strings +++ b/mac/Resources/en.lproj/Localizable.strings @@ -12,10 +12,7 @@ "placement.above" = "Above the Mac"; "placement.below" = "Below the Mac"; "menu.speed" = "Pointer speed on Android"; -"speed.slow" = "Slow"; -"speed.normal" = "Normal"; -"speed.fast" = "Fast"; -"speed.faster" = "Faster"; +"speed.multiplier" = "%d×"; "menu.commandAsControl" = "Command acts as Ctrl"; "menu.invertScroll" = "Invert scrolling on Android"; "menu.hotkey" = "Back to the Mac: ⌃⌥⌘B"; diff --git a/mac/Resources/pt-BR.lproj/Localizable.strings b/mac/Resources/pt-BR.lproj/Localizable.strings index 93d4fad..38e6022 100644 --- a/mac/Resources/pt-BR.lproj/Localizable.strings +++ b/mac/Resources/pt-BR.lproj/Localizable.strings @@ -12,10 +12,7 @@ "placement.above" = "Acima do Mac"; "placement.below" = "Abaixo do Mac"; "menu.speed" = "Velocidade do ponteiro no Android"; -"speed.slow" = "Lenta"; -"speed.normal" = "Normal"; -"speed.fast" = "Rápida"; -"speed.faster" = "Mais rápida"; +"speed.multiplier" = "%d×"; "menu.commandAsControl" = "Command funciona como Ctrl"; "menu.invertScroll" = "Inverter rolagem no Android"; "menu.hotkey" = "Voltar ao Mac: ⌃⌥⌘B"; diff --git a/mac/Sources/DroidBridge/AppDelegate.swift b/mac/Sources/DroidBridge/AppDelegate.swift index 80af30a..7e57c7f 100644 --- a/mac/Sources/DroidBridge/AppDelegate.swift +++ b/mac/Sources/DroidBridge/AppDelegate.swift @@ -115,8 +115,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate { let speed = NSMenuItem(title: L("menu.speed"), action: nil, keyEquivalent: "") let speedMenu = NSMenu() - for (key, value) in [("speed.slow", 0.5), ("speed.normal", 1.0), ("speed.fast", 1.5), ("speed.faster", 2.0)] { - let i = item(L(key), #selector(chooseSpeed(_:))) + for value in [1.0, 2.0, 3.0, 4.0, 6.0] { + let i = item(String(format: L("speed.multiplier"), Int(value)), #selector(chooseSpeed(_:))) i.representedObject = value i.state = Settings.speed == value ? .on : .off speedMenu.addItem(i) diff --git a/mac/Sources/DroidBridge/InputCapture.swift b/mac/Sources/DroidBridge/InputCapture.swift index ab75039..1f73a5b 100644 --- a/mac/Sources/DroidBridge/InputCapture.swift +++ b/mac/Sources/DroidBridge/InputCapture.swift @@ -32,6 +32,7 @@ final class InputCapture { private var keyboard = KeyboardState() private var scrollAccumulator = CGVector.zero private let cursor = CursorVisibility() + private var loggedTapFailure = false /// Starts the event tap. Fails until the app has the Accessibility permission. @discardableResult @@ -47,7 +48,10 @@ final class InputCapture { let capture = Unmanaged.fromOpaque(info!).takeUnretainedValue() return capture.handle(type, event) }, userInfo: me) else { - log.error("event tap not created (Accessibility permission missing?)") + if !loggedTapFailure { + loggedTapFailure = true + log.error("event tap not created (Accessibility permission missing?)") + } return false } self.tap = tap @@ -208,22 +212,35 @@ final class InputCapture { } /// Hides the cursor while the pointer is on the device. A background app can only hide the cursor -/// after setting the window server property "SetsCursorInBackground" (the approach Synergy/Barrier use). +/// after setting the window server property "SetsCursorInBackground" (what Synergy/Barrier do); as +/// that alone does not hide it on recent macOS, the app also comes to the front while the pointer is +/// away, hides its cursor there, and gives the focus back to the previous app on return. final class CursorVisibility { + private let log = Logger(subsystem: "dev.droidbridge", category: "cursor") private var hidden = false private var backgroundEnabled = false + private var previousApp: NSRunningApplication? func hide() { guard !hidden else { return } - enableBackgroundCursorControl() - CGDisplayHideCursor(CGMainDisplayID()) hidden = true + enableBackgroundCursorControl() + previousApp = NSWorkspace.shared.frontmostApplication + NSApp.activate(ignoringOtherApps: true) + NSCursor.hide() + let err = CGDisplayHideCursor(CGMainDisplayID()) + if err != .success { log.error("CGDisplayHideCursor: \(err.rawValue)") } } func show() { guard hidden else { return } - CGDisplayShowCursor(CGMainDisplayID()) hidden = false + CGDisplayShowCursor(CGMainDisplayID()) + NSCursor.unhide() + if let app = previousApp, app != NSRunningApplication.current { + app.activate() + } + previousApp = nil } private func enableBackgroundCursorControl() { @@ -232,8 +249,12 @@ final class CursorVisibility { typealias DefaultConnection = @convention(c) () -> Int32 typealias SetProperty = @convention(c) (Int32, Int32, CFString, CFTypeRef) -> Int32 let handle = dlopen(nil, RTLD_NOW) - guard let c = dlsym(handle, "_CGSDefaultConnection"), let s = dlsym(handle, "CGSSetConnectionProperty") else { return } + guard let c = dlsym(handle, "_CGSDefaultConnection"), let s = dlsym(handle, "CGSSetConnectionProperty") else { + log.error("window server functions not found") + return + } let connection = unsafeBitCast(c, to: DefaultConnection.self)() - _ = unsafeBitCast(s, to: SetProperty.self)(connection, connection, "SetsCursorInBackground" as CFString, kCFBooleanTrue) + let r = unsafeBitCast(s, to: SetProperty.self)(connection, connection, "SetsCursorInBackground" as CFString, kCFBooleanTrue) + log.info("SetsCursorInBackground: \(r)") } } diff --git a/mac/Sources/DroidBridge/Settings.swift b/mac/Sources/DroidBridge/Settings.swift index 4be234f..e2f367b 100644 --- a/mac/Sources/DroidBridge/Settings.swift +++ b/mac/Sources/DroidBridge/Settings.swift @@ -16,7 +16,7 @@ struct Settings { } static var speed: Double { - get { d.object(forKey: "speed") as? Double ?? 1.0 } + get { d.object(forKey: "speed") as? Double ?? 3.0 } set { d.set(newValue, forKey: "speed") } } diff --git a/mac/scripts/build-app.sh b/mac/scripts/build-app.sh index 2ab5d33..77fea5c 100755 --- a/mac/scripts/build-app.sh +++ b/mac/scripts/build-app.sh @@ -40,5 +40,10 @@ cat > "$APP/Contents/Info.plist" < PLIST +# A stable identity keeps the Accessibility permission across rebuilds (ad-hoc signatures lose it). +# scripts/create-signing-identity.sh makes one in its own keychain. +if [[ -n "${SIGN_KEYCHAIN:-}" ]]; then + security unlock-keychain -p "$(cat "${SIGN_KEYCHAIN_PASSWORD_FILE:?}")" "$SIGN_KEYCHAIN" +fi codesign --force --sign "$IDENTITY" --identifier dev.droidbridge.mac "$APP" echo "$APP" diff --git a/mac/scripts/create-signing-identity.sh b/mac/scripts/create-signing-identity.sh new file mode 100755 index 0000000..2123134 --- /dev/null +++ b/mac/scripts/create-signing-identity.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# Creates a self-signed code-signing identity ("DroidBridge Local Signing") in its own keychain, so +# local builds keep the same signature and macOS keeps the Accessibility permission across rebuilds. +# Then build with: +# SIGN_IDENTITY="DroidBridge Local Signing" SIGN_KEYCHAIN=~/Library/Keychains/droidbridge-signing.keychain-db \ +# SIGN_KEYCHAIN_PASSWORD_FILE=~/.config/droidbridge/keychain-password scripts/build-app.sh +set -euo pipefail +KC=$HOME/Library/Keychains/droidbridge-signing.keychain-db +PWFILE=$HOME/.config/droidbridge/keychain-password +TMP=$(mktemp -d) +trap 'rm -rf "$TMP"' EXIT + +cat > "$TMP/cert.cnf" </dev/null +P12PASS=$(openssl rand -hex 16) +openssl pkcs12 -export -legacy -inkey "$TMP/key.pem" -in "$TMP/cert.pem" -out "$TMP/id.p12" -passout "pass:$P12PASS" 2>/dev/null \ + || openssl pkcs12 -export -inkey "$TMP/key.pem" -in "$TMP/cert.pem" -out "$TMP/id.p12" -passout "pass:$P12PASS" + +mkdir -p "$(dirname "$PWFILE")" +openssl rand -hex 16 > "$PWFILE" +chmod 600 "$PWFILE" +security create-keychain -p "$(cat "$PWFILE")" "$KC" +security set-keychain-settings "$KC" +security unlock-keychain -p "$(cat "$PWFILE")" "$KC" +security import "$TMP/id.p12" -k "$KC" -P "$P12PASS" -T /usr/bin/codesign +security set-key-partition-list -S apple-tool:,apple: -s -k "$(cat "$PWFILE")" "$KC" >/dev/null +# codesign only finds identities in keychains on the search list. +security list-keychains -d user -s $(security list-keychains -d user | tr -d '"') "$KC" +security find-identity -p codesigning "$KC" From ddff117cc0068313dedff122bc95a5d697ca5cf7 Mon Sep 17 00:00:00 2001 From: beyondlevi Date: Wed, 7 Oct 2026 10:44:38 -0300 Subject: [PATCH 06/11] docs: README, architecture, wire protocol, NOTICE; CI build Co-Authored-By: Volund OS <287815646+volundos[bot]@users.noreply.github.com> --- .github/workflows/build.yml | 33 +++++++++++++++++++++ NOTICE | 7 +++++ README.md | 58 +++++++++++++++++++++++++++++++++++++ docs/architecture.md | 52 +++++++++++++++++++++++++++++++++ protocol/README.md | 36 +++++++++++++++++++++++ 5 files changed, 186 insertions(+) create mode 100644 .github/workflows/build.yml create mode 100644 NOTICE create mode 100644 README.md create mode 100644 docs/architecture.md create mode 100644 protocol/README.md diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..ee9f76c --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,33 @@ +name: build + +on: + push: + branches: [main] + pull_request: + +jobs: + build: + runs-on: macos-15 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 21 + - uses: android-actions/setup-android@v3 + with: + packages: platforms;android-36 build-tools;36.0.0 + - name: Server + run: server/build.sh + - name: Mac tests + working-directory: mac + run: swift test + - name: Mac app + working-directory: mac + run: scripts/build-app.sh && ditto -c -k --keepParent dist/DroidBridge.app dist/DroidBridge.zip + - uses: actions/upload-artifact@v4 + with: + name: DroidBridge + path: | + mac/dist/DroidBridge.zip + server/build/droidbridge-server.jar diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..2cb83af --- /dev/null +++ b/NOTICE @@ -0,0 +1,7 @@ +DroidBridge +Copyright 2026 Levi Nóbrega and contributors + +This product includes software from scrcpy (https://github.com/Genymobile/scrcpy), +Copyright (C) 2018 Genymobile, Copyright (C) 2018-2026 Romain Vimont, +licensed under the Apache License, Version 2.0. The unmodified scrcpy 4.1 server +sources are in server/third_party/ (license: server/third_party/SCRCPY-LICENSE). diff --git a/README.md b/README.md new file mode 100644 index 0000000..26156c1 --- /dev/null +++ b/README.md @@ -0,0 +1,58 @@ +# DroidBridge + +Use your Mac's mouse and keyboard on an Android phone or tablet. Push the pointer past the edge +of your Mac's screen and it shows up on the Android device; push it back and you're on the Mac. +The text clipboard is shared both ways. An open-source take on DeskDock, for macOS. + +- Real Android pointer and keyboard: the device sees a USB mouse and keyboard (UHID). +- Nothing to install on the device: the Mac starts a small server over adb. +- Text clipboard sync in both directions. +- Works with several displays: only edges that don't lead to another display count. +- English and Brazilian Portuguese. + +Status: early (0.1). USB only for now; Wi-Fi and file drag-and-drop are planned. + +## Requirements + +- macOS 13 or later. +- Android 8.1 or later with **USB debugging** on (Settings > About phone > tap Build number 7 times, + then Settings > Developer options > USB debugging). +- `adb` (Android platform-tools): `brew install android-platform-tools`. + +## Use + +1. Connect the device by USB and accept the debugging prompt on it. +2. Open DroidBridge. It lives in the menu bar. +3. Allow it in System Settings > Privacy & Security > Accessibility. +4. In the menu, pick where the device sits: right, left, above or below the Mac. +5. Push the pointer past that edge. + +Back to the Mac: push the pointer against the device edge facing the Mac, or press **⌃⌥⌘B**. + +By default Command acts as Ctrl on Android, so ⌘C / ⌘V / ⌘A work as expected (Control becomes Meta). +Turn it off in the menu to keep Command as Meta. + +To type accents and symbols as on your Mac layout, set the same layout for the +"droidbridge keyboard" in Android's Settings > Physical keyboard. + +## Build + +```sh +# Android server (needs ANDROID_HOME with platforms/android-36 and build-tools/36.0.0, JDK 17+) +server/build.sh +# Mac app (Xcode 15+), tests, and the .app bundle in mac/dist/ +cd mac && swift test && scripts/build-app.sh +``` + +Ad-hoc signed builds lose the Accessibility permission each time they are rebuilt. +`mac/scripts/create-signing-identity.sh` creates a local signing identity that keeps it. + +`tools/devtest.py` drives the server on a connected device without the Mac app. + +## How it works + +See [docs/architecture.md](docs/architecture.md) and the [wire protocol](protocol/README.md). + +## License + +Apache License 2.0. Includes the scrcpy server sources (Apache 2.0), see [NOTICE](NOTICE). diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..7309a82 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,52 @@ +# How DroidBridge works + +## The device side + +There is no app to install on the phone. The Mac pushes `droidbridge-server.jar` to +`/data/local/tmp` and runs it with `app_process` as the adb shell user, like scrcpy does. +The shell user may create input devices through `/dev/uhid` (Android 8.1 and later), so the +server registers a real USB-HID mouse and keyboard. Android then draws its own pointer, +accelerates it with its own settings, and treats keys exactly like a physical keyboard +(all characters, the IME, the hardware keyboard layout). + +The shell user can also read and write the clipboard in the background, which apps can't do +since Android 10. + +## Coming back to the Mac + +The mouse is relative and Android accelerates it, so the Mac can't compute where the pointer +is. The shell user can't monitor input events, and the system's cursor-position API is internal +to `system_server`. What the shell user can do is ask the input service for a dump: it lists each +device's hovering pointer in display coordinates, e.g. +`DeviceId(15):[... hoveringPointers=[Pointer(id=0, MOUSE) at (0, 1835.57)]]`. A dump takes +about 20 ms on a recent phone, so the server only takes one (at most every 30 ms) while the pointer +moves toward the edge that leads back to the Mac. Once the pointer touches that edge and keeps +being pushed against it, the server tells the Mac, with the position along the edge. + +Measured on a Galaxy Z Fold (Android 17), 2026-10-07: + +- UHID relative mouse: real pointer, class `CURSOR`. +- UHID absolute (X/Y) mouse: Android ignores the device. Not usable. +- Injected events (`InputManager.injectInputEvent`, as scrcpy's "SDK" mouse): exact position but + no pointer is drawn. + +## Entering the device + +When the Mac cursor is pushed out of a free screen edge, the Mac freezes its cursor +(`CGAssociateMouseAndMouseCursorPosition(false)`), hides it and sends `ENTER` with the position +along the edge. The server pushes the pointer into the corner of the entry edge, then walks it along +the edge, measuring with the dump, until it is within about 12 px of the matching position +(0.3-0.5 s). From then on the Mac forwards raw mouse deltas (times the speed setting), buttons, +scrolling and keys, and swallows them locally through a `CGEventTap`. + +## The Mac side + +A menu-bar app (Swift, no Xcode project: Swift Package Manager plus `mac/scripts/build-app.sh`). + +- `DroidBridgeCore`: wire protocol, screen-edge geometry (multiple displays: only edges that don't + lead to another display count), macOS key code to HID usage table. Unit-tested. +- `DroidBridge`: event tap, cursor hiding, adb link with reconnection, clipboard polling, menu. + +Permissions: Accessibility (to swallow events). Hiding the cursor from a background app is not +possible on recent macOS, so the app comes to the front while the pointer is on the device and gives +the focus back to the previous app on return. diff --git a/protocol/README.md b/protocol/README.md new file mode 100644 index 0000000..2e2d0f2 --- /dev/null +++ b/protocol/README.md @@ -0,0 +1,36 @@ +# DroidBridge wire protocol (version 1) + +The Mac app starts the server on the device over adb and connects to it through +`adb forward tcp: localabstract:droidbridge`. Every message, in both +directions, is: + +| Field | Size | +|---|---| +| type | u8 | +| payload length | u32, big-endian | +| payload | length bytes | + +All integers are big-endian. `side` is a side of the Android screen: +0 left, 1 right, 2 top, 3 bottom. `ratio` is a position along that edge, +0 at the top/left end and 65535 at the other. + +## Mac to Android + +| Type | Name | Payload | +|---|---|---| +| 0x01 | HELLO | u16 protocol version | +| 0x02 | ENTER | u8 side, u16 ratio: put the pointer on this edge | +| 0x03 | LEAVE | empty: release keys and buttons, stop watching the edge | +| 0x04 | MOUSE | u8 buttons (bit 0 left, 1 right, 2 middle, 3 back, 4 forward), i16 dx, i16 dy, i8 wheel, i8 horizontal wheel | +| 0x05 | KEYS | 8-byte HID boot keyboard report (modifiers, reserved, 6 usages) | +| 0x06 | CLIPBOARD | UTF-8 text to put on the device clipboard | +| 0x07 | PING | empty | + +## Android to Mac + +| Type | Name | Payload | +|---|---|---| +| 0x81 | DEVICE | u16 protocol version, u16 width, u16 height, UTF-8 model; sent first | +| 0x82 | EDGE | u8 side, u16 ratio: the pointer was pushed out of the return edge | +| 0x83 | CLIPBOARD | UTF-8 text copied on the device | +| 0x84 | PONG | empty | From 86bddd0048985c1c089481a3487c6cc89f4a3116 Mon Sep 17 00:00:00 2001 From: beyondlevi Date: Wed, 7 Oct 2026 10:51:40 -0300 Subject: [PATCH 07/11] server: apply the Mac keyboard layout to the UHID keyboard (LAYOUT message) Co-Authored-By: Volund OS <287815646+volundos[bot]@users.noreply.github.com> --- .../src/dev/droidbridge/KeyboardLayouts.java | 50 +++++++++++++++++++ server/src/dev/droidbridge/Protocol.java | 1 + server/src/dev/droidbridge/Session.java | 3 ++ tools/devtest.py | 18 +++++++ 4 files changed, 72 insertions(+) create mode 100644 server/src/dev/droidbridge/KeyboardLayouts.java diff --git a/server/src/dev/droidbridge/KeyboardLayouts.java b/server/src/dev/droidbridge/KeyboardLayouts.java new file mode 100644 index 0000000..4e108e9 --- /dev/null +++ b/server/src/dev/droidbridge/KeyboardLayouts.java @@ -0,0 +1,50 @@ +package dev.droidbridge; + +import android.os.IBinder; +import android.view.InputDevice; + +import java.lang.reflect.Method; + +/** + * Sets the Android keyboard layout of our UHID keyboard, so dead keys and symbols match the Mac's + * layout. Uses IInputManager.setKeyboardLayoutOverrideForInputDevice (Android 15+, allowed to the + * shell user through SET_KEYBOARD_LAYOUT). + */ +final class KeyboardLayouts { + private static final String PREFIX = "com.android.inputdevices/com.android.inputdevices.InputDeviceReceiver/keyboard_layout_"; + + private KeyboardLayouts() { + } + + /** {@code layout} is the suffix of an Android layout descriptor, e.g. "english_us_intl". */ + static boolean apply(String deviceName, String layout) { + if (!layout.matches("[a-z_]+")) { + Log.w("bad layout name " + layout); + return false; + } + try { + InputDevice device = null; + for (int id : InputDevice.getDeviceIds()) { + InputDevice d = InputDevice.getDevice(id); + if (d != null && deviceName.equals(d.getName())) { + device = d; + } + } + if (device == null) { + Log.w("keyboard not found for layout"); + return false; + } + Object identifier = InputDevice.class.getMethod("getIdentifier").invoke(device); + Class sm = Class.forName("android.os.ServiceManager"); + IBinder binder = (IBinder) sm.getMethod("getService", String.class).invoke(null, "input"); + Object im = Class.forName("android.hardware.input.IInputManager$Stub").getMethod("asInterface", IBinder.class).invoke(null, binder); + Method set = im.getClass().getMethod("setKeyboardLayoutOverrideForInputDevice", identifier.getClass(), String.class); + set.invoke(im, identifier, PREFIX + layout); + Log.i("keyboard layout " + layout); + return true; + } catch (ReflectiveOperationException | RuntimeException e) { + Log.e("cannot set keyboard layout " + layout, e); + return false; + } + } +} diff --git a/server/src/dev/droidbridge/Protocol.java b/server/src/dev/droidbridge/Protocol.java index 43250bc..2b65479 100644 --- a/server/src/dev/droidbridge/Protocol.java +++ b/server/src/dev/droidbridge/Protocol.java @@ -15,6 +15,7 @@ public final class Protocol { public static final int KEYS = 0x05; // 8-byte HID boot keyboard report public static final int CLIPBOARD = 0x06; // utf-8 text public static final int PING = 0x07; // empty + public static final int LAYOUT = 0x08; // utf-8 Android keyboard layout name, e.g. "english_us_intl" // Android -> Mac public static final int DEVICE = 0x81; // u16 protocol version, u16 width, u16 height, utf-8 model diff --git a/server/src/dev/droidbridge/Session.java b/server/src/dev/droidbridge/Session.java index 4b6dee9..6e24b7a 100644 --- a/server/src/dev/droidbridge/Session.java +++ b/server/src/dev/droidbridge/Session.java @@ -124,6 +124,9 @@ private void handle(int type, byte[] p) throws IOException { case Protocol.CLIPBOARD: setDeviceClipboard(new String(p, StandardCharsets.UTF_8)); break; + case Protocol.LAYOUT: + KeyboardLayouts.apply(KEYBOARD_NAME, new String(p, StandardCharsets.UTF_8)); + break; case Protocol.PING: send(Protocol.PONG, new byte[0]); break; diff --git a/tools/devtest.py b/tools/devtest.py index 78ee2ab..afa91a1 100644 --- a/tools/devtest.py +++ b/tools/devtest.py @@ -71,6 +71,24 @@ def main(): if t == 0x82: side, r = struct.unpack(">BH", p); got = (side, r / 65535); break print("edge:", got, f"after {time.time()-t0:.2f}s") + if test.startswith("type"): + # Types dead-key sequences with the given Android layout: devtest.py type: + layout = test.split(":", 1)[1] if ":" in test else "english_us_intl" + S.send(0x08, layout.encode()); time.sleep(0.8) + SHIFT = 0x02 + def key(mods, usage): + S.send(0x05, bytes([mods, 0, usage, 0, 0, 0, 0, 0])); time.sleep(0.03) + S.send(0x05, bytes(8)); time.sleep(0.03) + A, C, E, O, U, SPACE, ENTER = 0x04, 0x06, 0x08, 0x12, 0x18, 0x2C, 0x28 + QUOTE, GRAVE, SIX = 0x34, 0x35, 0x23 + seqs = [("'a", [(0, QUOTE), (0, A)]), ("~a", [(SHIFT, GRAVE), (0, A)]), ("`a", [(0, GRAVE), (0, A)]), + ("^e", [(SHIFT, SIX), (0, E)]), ('"u', [(SHIFT, QUOTE), (0, U)]), ("'c", [(0, QUOTE), (0, C)]), + ("~o", [(SHIFT, GRAVE), (0, O)]), ("' space", [(0, QUOTE), (0, SPACE)])] + for name, keys in seqs: + for k in keys: key(*k) + key(0, SPACE) + key(0, ENTER) + print("typed:", " | ".join(n for n, _ in seqs)) if test == "clipboard": S.send(0x06, "droidbridge mac->android ção".encode()) time.sleep(0.5) From 06d31c4ee29239aaebc3eb9155dce941f76c4e54 Mon Sep 17 00:00:00 2001 From: beyondlevi Date: Wed, 7 Oct 2026 10:55:49 -0300 Subject: [PATCH 08/11] mac: Arrange Screens window (passage on any free display edge), keyboard layout follows the Mac Co-Authored-By: Volund OS <287815646+volundos[bot]@users.noreply.github.com> --- mac/Resources/en.lproj/Localizable.strings | 23 +- mac/Resources/pt-BR.lproj/Localizable.strings | 23 +- mac/Sources/DroidBridge/AppDelegate.swift | 55 ++- .../DroidBridge/ArrangementWindow.swift | 319 ++++++++++++++++++ mac/Sources/DroidBridge/Displays.swift | 45 +++ mac/Sources/DroidBridge/InputCapture.swift | 29 +- mac/Sources/DroidBridge/Settings.swift | 23 +- .../DroidBridgeCore/EdgeGeometry.swift | 191 ++++++++--- .../DroidBridgeCore/KeyboardLayoutMap.swift | 32 ++ mac/Sources/DroidBridgeCore/Wire.swift | 5 +- .../DroidBridgeCoreTests/CoreTests.swift | 99 ++++-- 11 files changed, 731 insertions(+), 113 deletions(-) create mode 100644 mac/Sources/DroidBridge/ArrangementWindow.swift create mode 100644 mac/Sources/DroidBridge/Displays.swift create mode 100644 mac/Sources/DroidBridgeCore/KeyboardLayoutMap.swift diff --git a/mac/Resources/en.lproj/Localizable.strings b/mac/Resources/en.lproj/Localizable.strings index 8c5b1ca..69df3e7 100644 --- a/mac/Resources/en.lproj/Localizable.strings +++ b/mac/Resources/en.lproj/Localizable.strings @@ -6,14 +6,27 @@ "status.failed" = "Could not connect: %@"; "menu.grantAccessibility" = "Allow Accessibility access…"; "menu.share" = "Share mouse and keyboard"; -"menu.placement" = "Android device position"; -"placement.right" = "Right of the Mac"; -"placement.left" = "Left of the Mac"; -"placement.above" = "Above the Mac"; -"placement.below" = "Below the Mac"; "menu.speed" = "Pointer speed on Android"; "speed.multiplier" = "%d×"; "menu.commandAsControl" = "Command acts as Ctrl"; "menu.invertScroll" = "Invert scrolling on Android"; "menu.hotkey" = "Back to the Mac: ⌃⌥⌘B"; "menu.quit" = "Quit DroidBridge"; +"menu.arrange" = "Arrange Screens…"; +"arrangement.title" = "Arrange Screens"; +"arrangement.hint" = "Drag the Android device against an edge of a display. The pointer only passes through the highlighted stretch; along the rest of the edge it stops, as between displays of different sizes."; +"arrangement.passage" = "Passage"; +"arrangement.size" = "Size of the Android device in the arrangement"; +"arrangement.sizeHint" = "Larger means a longer passage."; +"arrangement.reset" = "Restore Default"; +"arrangement.done" = "Done"; +"arrangement.device" = "Android"; +"edge.right" = "right"; +"edge.left" = "left"; +"edge.top" = "top"; +"edge.bottom" = "bottom"; +"axis.height" = "height"; +"axis.width" = "width"; +"passage.text" = "%1$@: %2$@ edge, from %3$d%% to %4$d%% of its %5$@."; +"passage.drop" = "Drop to attach to %1$@: %2$@ edge, from %3$d%% to %4$d%% of its %5$@."; +"passage.enters" = "The pointer enters through the Android device's %@ edge at the matching position."; diff --git a/mac/Resources/pt-BR.lproj/Localizable.strings b/mac/Resources/pt-BR.lproj/Localizable.strings index 38e6022..f795dd0 100644 --- a/mac/Resources/pt-BR.lproj/Localizable.strings +++ b/mac/Resources/pt-BR.lproj/Localizable.strings @@ -6,14 +6,27 @@ "status.failed" = "Não foi possível conectar: %@"; "menu.grantAccessibility" = "Permitir acesso de Acessibilidade…"; "menu.share" = "Compartilhar mouse e teclado"; -"menu.placement" = "Posição do Android"; -"placement.right" = "À direita do Mac"; -"placement.left" = "À esquerda do Mac"; -"placement.above" = "Acima do Mac"; -"placement.below" = "Abaixo do Mac"; "menu.speed" = "Velocidade do ponteiro no Android"; "speed.multiplier" = "%d×"; "menu.commandAsControl" = "Command funciona como Ctrl"; "menu.invertScroll" = "Inverter rolagem no Android"; "menu.hotkey" = "Voltar ao Mac: ⌃⌥⌘B"; "menu.quit" = "Sair do DroidBridge"; +"menu.arrange" = "Organizar telas…"; +"arrangement.title" = "Organizar telas"; +"arrangement.hint" = "Arraste o Android até encostar numa borda de um monitor. O ponteiro passa só pelo trecho destacado; no resto da borda ele para, como entre monitores de tamanhos diferentes."; +"arrangement.passage" = "Passagem"; +"arrangement.size" = "Tamanho do Android na organização"; +"arrangement.sizeHint" = "Maior = trecho de passagem mais longo."; +"arrangement.reset" = "Restaurar padrão"; +"arrangement.done" = "Concluir"; +"arrangement.device" = "Android"; +"edge.right" = "direita"; +"edge.left" = "esquerda"; +"edge.top" = "de cima"; +"edge.bottom" = "de baixo"; +"axis.height" = "altura"; +"axis.width" = "largura"; +"passage.text" = "%1$@: borda %2$@, de %3$d%% a %4$d%% da %5$@."; +"passage.drop" = "Solte para encostar em %1$@: borda %2$@, de %3$d%% a %4$d%% da %5$@."; +"passage.enters" = "O ponteiro entra pela borda %@ do Android, na posição correspondente."; diff --git a/mac/Sources/DroidBridge/AppDelegate.swift b/mac/Sources/DroidBridge/AppDelegate.swift index 7e57c7f..0be5ba1 100644 --- a/mac/Sources/DroidBridge/AppDelegate.swift +++ b/mac/Sources/DroidBridge/AppDelegate.swift @@ -1,5 +1,6 @@ import AppKit import ApplicationServices +import Carbon import DroidBridgeCore import os @@ -11,6 +12,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate { private let clipboard = ClipboardSync() private var statusItem: NSStatusItem! private var tapTimer: Timer? + private let arrangementWindow = ArrangementWindowController() + private var passage: Passage? private let log = Logger(subsystem: "dev.droidbridge", category: "app") func applicationDidFinishLaunching(_ notification: Notification) { @@ -26,14 +29,28 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate { return true } capture.onRemoteChanged = { [weak self] _ in self?.updateIcon() } + capture.passage = { [weak self] in self?.passage } + refreshPassage() + arrangementWindow.model.onChange = { [weak self] in self?.refreshPassage() } + NotificationCenter.default.addObserver(forName: NSApplication.didChangeScreenParametersNotification, object: nil, + queue: .main) { [weak self] _ in + self?.refreshPassage() + if self?.capture.isRemote == false { self?.arrangementWindow.model.reload() } + } + DistributedNotificationCenter.default().addObserver( + forName: NSNotification.Name(kTISNotifySelectedKeyboardInputSourceChanged as String), object: nil, queue: .main + ) { [weak self] _ in self?.sendKeyboardLayout() } clipboard.send = { [weak self] text in guard let self, case .connected = self.link.state else { return } self.link.send(Wire.clipboard(text)) } link.onState = { [weak self] state in guard let self else { return } - if case .connected = state { + if case let .connected(model, width, height) = state { self.clipboard.pushCurrent() + self.sendKeyboardLayout() + self.arrangementWindow.model.deviceName = model + self.arrangementWindow.model.deviceSize = CGSize(width: width, height: height) } else { self.capture.returnToMac() } @@ -59,6 +76,24 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate { link.stop() } + private func refreshPassage() { + passage = Arrangements.passage(for: DisplayInfo.all()) + } + + /// Makes the device's keyboard layout match the Mac's current one, so dead keys work the same. + private func sendKeyboardLayout() { + guard case .connected = link.state, + let source = TISCopyCurrentKeyboardLayoutInputSource()?.takeRetainedValue(), + let raw = TISGetInputSourceProperty(source, kTISPropertyInputSourceID) else { return } + let id = Unmanaged.fromOpaque(raw).takeUnretainedValue() as String + if let layout = KeyboardLayoutMap.androidLayout(forInputSource: id) { + log.info("keyboard layout \(id, privacy: .public) -> \(layout, privacy: .public)") + link.send(Wire.layout(layout)) + } else { + log.info("no Android layout for \(id, privacy: .public)") + } + } + private func startTapWhenAllowed() { if capture.start() { return } tapTimer = Timer.scheduledTimer(withTimeInterval: 2, repeats: true) { [weak self] t in @@ -70,7 +105,6 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate { } private func applySettings() { - capture.options.placement = Settings.placement capture.options.speed = Settings.speed capture.options.commandAsControl = Settings.commandAsControl capture.options.invertScroll = Settings.invertScroll @@ -102,16 +136,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate { share.state = Settings.enabled ? .on : .off menu.addItem(share) - let placement = NSMenuItem(title: L("menu.placement"), action: nil, keyEquivalent: "") - let placementMenu = NSMenu() - for p in Placement.allCases { - let i = item(L("placement.\(p.rawValue)"), #selector(choosePlacement(_:))) - i.representedObject = p.rawValue - i.state = Settings.placement == p ? .on : .off - placementMenu.addItem(i) - } - placement.submenu = placementMenu - menu.addItem(placement) + menu.addItem(item(L("menu.arrange"), #selector(openArrangement))) let speed = NSMenuItem(title: L("menu.speed"), action: nil, keyEquivalent: "") let speedMenu = NSMenu() @@ -165,10 +190,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate { if !Settings.enabled { capture.returnToMac() } } - @objc private func choosePlacement(_ sender: NSMenuItem) { - guard let raw = sender.representedObject as? String, let p = Placement(rawValue: raw) else { return } - Settings.placement = p - applySettings() + @objc private func openArrangement() { + arrangementWindow.show() } @objc private func chooseSpeed(_ sender: NSMenuItem) { diff --git a/mac/Sources/DroidBridge/ArrangementWindow.swift b/mac/Sources/DroidBridge/ArrangementWindow.swift new file mode 100644 index 0000000..6d88f3f --- /dev/null +++ b/mac/Sources/DroidBridge/ArrangementWindow.swift @@ -0,0 +1,319 @@ +import AppKit +import DroidBridgeCore +import SwiftUI + +/// "Arrange screens": drag the Android device against an edge of a Mac display. The overlap is the +/// passage; the size slider sets how long it is. +final class ArrangementModel: ObservableObject { + @Published var displays: [DisplayInfo] = [] + @Published var arrangement: Arrangement? + @Published var deviceName: String? + @Published var deviceSize: CGSize? + /// Drag offset of the device, in global points. + @Published var drag: CGSize? + + var onChange: (() -> Void)? + + static let snapDistance: CGFloat = 24 + + func reload() { + displays = DisplayInfo.all() + arrangement = Arrangements.current(for: displays) + } + + var display: DisplayInfo? { + guard let a = arrangement else { return nil } + return displays.first { $0.key == a.displayKey } + } + + /// Device width / height as it is held now (portrait by default). + var aspect: CGFloat { + guard let s = deviceSize, s.width > 0, s.height > 0 else { return 1080.0 / 2340.0 } + return s.width / s.height + } + + /// The device rectangle, in global points, for an arrangement. + func deviceRect(_ a: Arrangement, on d: CGRect) -> CGRect { + let vertical = a.edge.isVertical + let length = CGFloat(a.end - a.start) * (vertical ? d.height : d.width) + let thickness = vertical ? length * aspect : length / aspect + switch a.edge { + case .right: return CGRect(x: d.maxX, y: d.minY + CGFloat(a.start) * d.height, width: thickness, height: length) + case .left: return CGRect(x: d.minX - thickness, y: d.minY + CGFloat(a.start) * d.height, width: thickness, height: length) + case .top: return CGRect(x: d.minX + CGFloat(a.start) * d.width, y: d.minY - thickness, width: length, height: thickness) + case .bottom: return CGRect(x: d.minX + CGFloat(a.start) * d.width, y: d.maxY, width: length, height: thickness) + } + } + + /// Where the dragged device would attach, if it is close enough to a free edge. + func candidate(for ghost: CGRect, scale: CGFloat) -> Arrangement? { + guard let current = arrangement else { return nil } + let reach = Self.snapDistance / scale + var best: (Arrangement, CGFloat)? + for d in displays { + let r = d.bounds + for edge in Edge.allCases { + let gap: CGFloat + let center: CGFloat + switch edge { + case .right: gap = abs(ghost.minX - r.maxX); center = (ghost.midY - r.minY) / r.height + case .left: gap = abs(ghost.maxX - r.minX); center = (ghost.midY - r.minY) / r.height + case .top: gap = abs(ghost.maxY - r.minY); center = (ghost.midX - r.minX) / r.width + case .bottom: gap = abs(ghost.minY - r.maxY); center = (ghost.midX - r.minX) / r.width + } + guard gap <= reach, center > -0.15, center < 1.15 else { continue } + let free = EdgeGeometry.freeIntervals(of: r, edge: edge, displays: displays.map(\.bounds)) + guard let s = EdgeGeometry.snap(center: Double(center), length: current.size, free: free) else { continue } + let a = Arrangement(displayKey: d.key, edge: edge, start: s.lowerBound, end: s.upperBound, size: current.size) + if best == nil || gap < best!.1 { best = (a, gap) } + } + } + return best?.0 + } + + func commit(_ a: Arrangement) { + arrangement = a + Settings.arrangement = a + onChange?() + } + + func setSize(_ size: Double) { + guard var a = arrangement, let d = display else { return } + let free = EdgeGeometry.freeIntervals(of: d.bounds, edge: a.edge, displays: displays.map(\.bounds)) + guard let s = EdgeGeometry.snap(center: (a.start + a.end) / 2, length: size, free: free) else { return } + a.size = size + a.start = s.lowerBound + a.end = s.upperBound + commit(a) + } + + func reset() { + Settings.arrangement = nil + reload() + onChange?() + } + + func describe(_ a: Arrangement, format: String) -> String { + let name = displays.first { $0.key == a.displayKey }?.name ?? "?" + return String(format: L(format), name, L("edge.\(a.edge.rawValue)"), Int((a.start * 100).rounded()), + Int((a.end * 100).rounded()), L(a.edge.isVertical ? "axis.height" : "axis.width")) + } +} + +struct ArrangementView: View { + @ObservedObject var model: ArrangementModel + var onDone: () -> Void + static let accent = Color(red: 0x2F / 255, green: 0xBF / 255, blue: 0x71 / 255) + + var body: some View { + VStack(alignment: .leading, spacing: 16) { + Text(L("arrangement.hint")) + .font(.system(size: 13)) + .foregroundColor(Color(white: 0.28)) + .fixedSize(horizontal: false, vertical: true) + GeometryReader { geo in + ArrangementCanvas(model: model, size: geo.size) + } + .frame(height: 320) + .background(RoundedRectangle(cornerRadius: 10).fill(Color(red: 0.914, green: 0.914, blue: 0.929))) + .overlay(RoundedRectangle(cornerRadius: 10).stroke(Color(white: 0.855))) + HStack(alignment: .top, spacing: 16) { + passageCard + sizeCard + } + HStack { + Button(L("arrangement.reset")) { model.reset() } + Spacer() + Button(L("arrangement.done"), action: onDone).keyboardShortcut(.defaultAction) + } + } + .padding(EdgeInsets(top: 20, leading: 24, bottom: 20, trailing: 24)) + .frame(width: 880) + } + + private var passageCard: some View { + VStack(alignment: .leading, spacing: 6) { + HStack(spacing: 8) { + Circle().fill(Self.accent).frame(width: 10, height: 10) + Text(L("arrangement.passage")).font(.system(size: 13, weight: .semibold)) + } + if let a = model.arrangement { + Text(model.describe(a, format: "passage.text")).font(.system(size: 13)) + Text(String(format: L("passage.enters"), L("edge.\(sideKey(a.edge.androidSide))"))) + .font(.system(size: 12)).foregroundColor(.secondary) + } + } + .frame(maxWidth: .infinity, alignment: .leading) + .padding(EdgeInsets(top: 14, leading: 16, bottom: 14, trailing: 16)) + .background(RoundedRectangle(cornerRadius: 10).fill(Color.white)) + .overlay(RoundedRectangle(cornerRadius: 10).stroke(Color(white: 0.878))) + } + + private var sizeCard: some View { + VStack(alignment: .leading, spacing: 12) { + Text(L("arrangement.size")).font(.system(size: 13, weight: .semibold)) + Slider(value: Binding(get: { model.arrangement?.size ?? 0.6 }, set: { model.setSize($0) }), in: 0.2...1) + Text(L("arrangement.sizeHint")).font(.system(size: 12)).foregroundColor(.secondary) + } + .frame(maxWidth: .infinity, alignment: .leading) + .padding(EdgeInsets(top: 14, leading: 16, bottom: 14, trailing: 16)) + .background(RoundedRectangle(cornerRadius: 10).fill(Color.white)) + .overlay(RoundedRectangle(cornerRadius: 10).stroke(Color(white: 0.878))) + } + + private func sideKey(_ s: Side) -> String { + switch s { + case .left: return "left" + case .right: return "right" + case .top: return "top" + case .bottom: return "bottom" + } + } +} + +/// The displays and the device, drawn to scale. +private struct ArrangementCanvas: View { + @ObservedObject var model: ArrangementModel + let size: CGSize + + var body: some View { + let t = transform() + ZStack(alignment: .topLeading) { + ForEach(model.displays) { d in + displayView(d).frame(width: d.bounds.width * t.scale, height: d.bounds.height * t.scale) + .offset(t.point(d.bounds.origin)) + } + if let a = model.arrangement, let d = model.display { + let rect = model.deviceRect(a, on: d.bounds) + let dragging = model.drag != nil + passageBar(a, on: d.bounds, t: t, color: dragging ? Color(white: 0.78) : ArrangementView.accent) + if let drag = model.drag, + let c = model.candidate(for: rect.offsetBy(dx: drag.width, dy: drag.height), scale: t.scale), + let cd = model.displays.first(where: { $0.key == c.displayKey }) { + passageBar(c, on: cd.bounds, t: t, color: ArrangementView.accent) + let target = model.deviceRect(c, on: cd.bounds) + RoundedRectangle(cornerRadius: 10) + .stroke(ArrangementView.accent, style: StrokeStyle(lineWidth: 2, dash: [6, 4])) + .frame(width: target.width * t.scale, height: target.height * t.scale) + .offset(t.point(target.origin)) + } + // One view for both states, so the drag gesture survives the switch. + let shown = model.drag.map { rect.offsetBy(dx: $0.width, dy: $0.height) } ?? rect + deviceView(ghost: dragging).frame(width: shown.width * t.scale, height: shown.height * t.scale) + .offset(t.point(shown.origin)) + .gesture(dragGesture(rect: rect, t: t)) + if dragging, let drag = model.drag, + let c = model.candidate(for: rect.offsetBy(dx: drag.width, dy: drag.height), scale: t.scale) { + Text(model.describe(c, format: "passage.drop")) + .font(.system(size: 12, weight: .medium)) + .padding(EdgeInsets(top: 4, leading: 8, bottom: 4, trailing: 8)) + .background(Capsule().fill(Color.white.opacity(0.92))) + .offset(x: 10, y: 10) + } + } + } + .frame(width: size.width, height: size.height, alignment: .topLeading) + .clipped() + } + + private func dragGesture(rect: CGRect, t: Transform) -> some Gesture { + DragGesture(minimumDistance: 2) + .onChanged { v in + model.drag = CGSize(width: v.translation.width / t.scale, height: v.translation.height / t.scale) + } + .onEnded { v in + let ghost = rect.offsetBy(dx: v.translation.width / t.scale, dy: v.translation.height / t.scale) + if let c = model.candidate(for: ghost, scale: t.scale) { model.commit(c) } + model.drag = nil + } + } + + private func displayView(_ d: DisplayInfo) -> some View { + VStack(spacing: 0) { + if d.isMain { + Rectangle().fill(Color(white: 0.96)).frame(height: 8) + } + Text(d.name) + .font(.system(size: 12, weight: .semibold)) + .foregroundColor(.white) + .lineLimit(2) + .multilineTextAlignment(.center) + .padding(4) + .frame(maxWidth: .infinity, maxHeight: .infinity) + } + .background(Color(red: 0.31, green: 0.43, blue: 0.56)) + .clipShape(RoundedRectangle(cornerRadius: 4)) + .overlay(RoundedRectangle(cornerRadius: 4).stroke(Color(red: 0.24, green: 0.34, blue: 0.45), lineWidth: 2)) + } + + private func deviceView(ghost: Bool) -> some View { + VStack(spacing: 4) { + Image(systemName: "iphone").font(.system(size: 16)).foregroundColor(.white) + Text(model.deviceName ?? L("arrangement.device")) + .font(.system(size: 12, weight: .semibold)).foregroundColor(.white).lineLimit(1) + if !ghost, let s = model.deviceSize { + Text("\(Int(s.width)) × \(Int(s.height))").font(.system(size: 11)).foregroundColor(Color(white: 0.72)) + } + } + .padding(4) + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(RoundedRectangle(cornerRadius: 10).fill(Color(red: 0.125, green: 0.129, blue: 0.141).opacity(ghost ? 0.82 : 1))) + .overlay(RoundedRectangle(cornerRadius: 10).stroke(ghost ? Color.white : ArrangementView.accent, lineWidth: 2)) + .shadow(color: .black.opacity(ghost ? 0.28 : 0), radius: 12, y: 6) + .contentShape(Rectangle()) + } + + private func passageBar(_ a: Arrangement, on d: CGRect, t: Transform, color: Color) -> some View { + let r: CGRect + switch a.edge { + case .right: r = CGRect(x: d.maxX, y: d.minY + CGFloat(a.start) * d.height, width: 0, height: CGFloat(a.end - a.start) * d.height) + case .left: r = CGRect(x: d.minX, y: d.minY + CGFloat(a.start) * d.height, width: 0, height: CGFloat(a.end - a.start) * d.height) + case .top: r = CGRect(x: d.minX + CGFloat(a.start) * d.width, y: d.minY, width: CGFloat(a.end - a.start) * d.width, height: 0) + case .bottom: r = CGRect(x: d.minX + CGFloat(a.start) * d.width, y: d.maxY, width: CGFloat(a.end - a.start) * d.width, height: 0) + } + let p = t.point(r.origin) + let w = max(r.width * t.scale, 6) + let h = max(r.height * t.scale, 6) + return RoundedRectangle(cornerRadius: 3).fill(color) + .frame(width: w, height: h) + .offset(x: p.width - (r.width == 0 ? 3 : 0), y: p.height - (r.height == 0 ? 3 : 0)) + } + + struct Transform { + let scale: CGFloat + let origin: CGPoint + func point(_ p: CGPoint) -> CGSize { CGSize(width: (p.x - origin.x) * scale, height: (p.y - origin.y) * scale) } + } + + /// Fits the displays, with room around them for the device, into the view. + private func transform() -> Transform { + let union = model.displays.map(\.bounds).reduce(CGRect.null) { $0.union($1) } + guard !union.isNull, size.width > 0 else { return Transform(scale: 1, origin: .zero) } + let margin = max(union.width, union.height) * 0.28 + let area = union.insetBy(dx: -margin, dy: -margin) + let scale = min(size.width / area.width, size.height / area.height) + let origin = CGPoint(x: union.midX - size.width / scale / 2, y: union.midY - size.height / scale / 2) + return Transform(scale: scale, origin: origin) + } +} + +/// Hosts the view in a window; one at a time. +final class ArrangementWindowController { + private var window: NSWindow? + let model = ArrangementModel() + + func show() { + model.reload() + if window == nil { + let host = NSHostingController(rootView: ArrangementView(model: model, onDone: { [weak self] in self?.window?.close() })) + let w = NSWindow(contentViewController: host) + w.title = L("arrangement.title") + w.styleMask = [.titled, .closable] + w.isReleasedWhenClosed = false + w.center() + window = w + } + NSApp.activate(ignoringOtherApps: true) + window?.makeKeyAndOrderFront(nil) + } +} diff --git a/mac/Sources/DroidBridge/Displays.swift b/mac/Sources/DroidBridge/Displays.swift new file mode 100644 index 0000000..73fd74b --- /dev/null +++ b/mac/Sources/DroidBridge/Displays.swift @@ -0,0 +1,45 @@ +import AppKit +import DroidBridgeCore + +/// A Mac display, with a key that stays the same across reconnections and reboots. +struct DisplayInfo: Equatable, Identifiable { + let id: CGDirectDisplayID + let key: String + let name: String + let bounds: CGRect + let isMain: Bool + + static func all() -> [DisplayInfo] { + var count: UInt32 = 0 + CGGetActiveDisplayList(0, nil, &count) + var ids = [CGDirectDisplayID](repeating: 0, count: Int(count)) + CGGetActiveDisplayList(count, &ids, &count) + let names = Dictionary(NSScreen.screens.compactMap { s -> (CGDirectDisplayID, String)? in + guard let n = s.deviceDescription[NSDeviceDescriptionKey("NSScreenNumber")] as? NSNumber else { return nil } + return (CGDirectDisplayID(n.uint32Value), s.localizedName) + }, uniquingKeysWith: { a, _ in a }) + return ids.prefix(Int(count)).map { id in + let key = CGDisplayIsBuiltin(id) != 0 ? "builtin" + : "\(CGDisplayVendorNumber(id))-\(CGDisplayModelNumber(id))-\(CGDisplaySerialNumber(id))" + return DisplayInfo(id: id, key: key, name: names[id] ?? "Display \(id)", bounds: CGDisplayBounds(id), + isMain: CGDisplayIsMain(id) != 0) + } + } +} + +/// Resolves the saved arrangement against the displays connected now. +enum Arrangements { + static func passage(for displays: [DisplayInfo]) -> Passage? { + if let a = Settings.arrangement, let d = displays.first(where: { $0.key == a.displayKey }) { + return Passage(display: d.bounds, edge: a.edge, start: a.start, end: a.end) + } + return EdgeGeometry.defaultPassage(edge: Settings.legacyEdge, displays: displays.map(\.bounds)) + } + + /// The arrangement shown in the window: the saved one, or the default turned into one. + static func current(for displays: [DisplayInfo]) -> Arrangement? { + if let a = Settings.arrangement, displays.contains(where: { $0.key == a.displayKey }) { return a } + guard let p = passage(for: displays), let d = displays.first(where: { $0.bounds == p.display }) else { return nil } + return Arrangement(displayKey: d.key, edge: p.edge, start: p.start, end: p.end, size: p.end - p.start) + } +} diff --git a/mac/Sources/DroidBridge/InputCapture.swift b/mac/Sources/DroidBridge/InputCapture.swift index 1f73a5b..fab7808 100644 --- a/mac/Sources/DroidBridge/InputCapture.swift +++ b/mac/Sources/DroidBridge/InputCapture.swift @@ -7,7 +7,6 @@ import os /// forwards them to the device instead. final class InputCapture { struct Options { - var placement: Placement = .right var speed: Double = 1.0 var commandAsControl = true var invertScroll = false @@ -20,12 +19,14 @@ final class InputCapture { var onRemoteChanged: ((Bool) -> Void)? /// Whether crossing to the device is allowed (a device is connected and sharing is on). var canCross: () -> Bool = { false } + /// The passage to the device for the current displays (see Arrangements). + var passage: () -> Passage? = { nil } private(set) var isRemote = false private let log = Logger(subsystem: "dev.droidbridge", category: "input") private var tap: CFMachPort? private var source: CFRunLoopSource? - private var crossedDisplay: CGRect = .zero + private var crossed: Passage? /// After coming back, the cursor must move away from the edge before it can cross again. private var armed = true private var buttons: UInt8 = 0 @@ -69,13 +70,14 @@ final class InputCapture { send?(Wire.leave()) keyboard.reset() buttons = 0 - let r = ratio ?? 0.5 - let p = EdgeGeometry.returnPoint(display: crossedDisplay, ratio: r, placement: options.placement) - CGWarpMouseCursorPosition(p) + if let passage = crossed { + let p = EdgeGeometry.returnPoint(passage: passage, androidRatio: ratio ?? 0.5) + CGWarpMouseCursorPosition(p) + log.info("back on the Mac at \(p.x), \(p.y)") + } CGAssociateMouseAndMouseCursorPosition(1) cursor.show() armed = false - log.info("back on the Mac at \(p.x), \(p.y)") onRemoteChanged?(false) } @@ -99,23 +101,24 @@ final class InputCapture { private func checkCrossing(_ event: CGEvent) { let p = event.location let delta = CGVector(dx: event.getDoubleValueField(.mouseEventDeltaX), dy: event.getDoubleValueField(.mouseEventDeltaY)) - let displays = Self.displays() if !armed { - if let d = displays.first(where: { $0.contains(p) }), - EdgeGeometry.isClear(of: d, at: p, placement: options.placement) || d != crossedDisplay { + if let last = crossed, !last.display.contains(p) || EdgeGeometry.isClear(of: last, at: p) { + armed = true + } else if crossed == nil { armed = true } return } - guard canCross(), let c = EdgeGeometry.crossing(at: p, delta: delta, placement: options.placement, displays: displays) else { + guard canCross(), let passage = passage(), + let ratio = EdgeGeometry.crossing(at: p, delta: delta, passage: passage, displays: Self.displays()) else { return } isRemote = true - crossedDisplay = c.display + crossed = passage CGAssociateMouseAndMouseCursorPosition(0) cursor.hide() - send?(Wire.enter(side: options.placement.androidSide, ratio: c.ratio)) - log.info("to Android, ratio \(c.ratio)") + send?(Wire.enter(side: passage.edge.androidSide, ratio: ratio)) + log.info("to Android, ratio \(ratio)") onRemoteChanged?(true) } diff --git a/mac/Sources/DroidBridge/Settings.swift b/mac/Sources/DroidBridge/Settings.swift index e2f367b..b6026bb 100644 --- a/mac/Sources/DroidBridge/Settings.swift +++ b/mac/Sources/DroidBridge/Settings.swift @@ -10,9 +10,26 @@ struct Settings { set { d.set(newValue, forKey: "enabled") } } - static var placement: Placement { - get { Placement(rawValue: d.string(forKey: "placement") ?? "") ?? .right } - set { d.set(newValue.rawValue, forKey: "placement") } + /// Where the device sits; nil means the default (the outermost right edge). + static var arrangement: Arrangement? { + get { d.data(forKey: "arrangement").flatMap { try? JSONDecoder().decode(Arrangement.self, from: $0) } } + set { + if let newValue, let data = try? JSONEncoder().encode(newValue) { + d.set(data, forKey: "arrangement") + } else { + d.removeObject(forKey: "arrangement") + } + } + } + + /// The side chosen in version 0.1 ("Android device position"), used until an arrangement is saved. + static var legacyEdge: Edge { + switch d.string(forKey: "placement") { + case "left": return .left + case "above": return .top + case "below": return .bottom + default: return .right + } } static var speed: Double { diff --git a/mac/Sources/DroidBridgeCore/EdgeGeometry.swift b/mac/Sources/DroidBridgeCore/EdgeGeometry.swift index 6a33d81..3170961 100644 --- a/mac/Sources/DroidBridgeCore/EdgeGeometry.swift +++ b/mac/Sources/DroidBridgeCore/EdgeGeometry.swift @@ -1,24 +1,58 @@ import CoreGraphics -/// Where the Android device sits relative to the Mac's screens. -public enum Placement: String, CaseIterable { - case right, left, above, below +/// An edge of a Mac display. +public enum Edge: String, Codable, CaseIterable { + case right, left, top, bottom /// The Android screen edge the pointer enters through (and leaves through). public var androidSide: Side { switch self { case .right: return .left case .left: return .right - case .above: return .bottom - case .below: return .top + case .top: return .bottom + case .bottom: return .top } } + + /// Left and right edges run vertically; positions along them are heights. + public var isVertical: Bool { self == .left || self == .right } +} + +/// Where the pointer passes between the Mac and the device: a stretch of one display's edge. +/// `start` and `end` are fractions along the edge, from the top (vertical edges) or the left. +public struct Passage: Equatable { + public var display: CGRect + public var edge: Edge + public var start: Double + public var end: Double + + public init(display: CGRect, edge: Edge, start: Double, end: Double) { + self.display = display + self.edge = edge + self.start = start + self.end = end + } + + /// The edge length in points. + public var edgeLength: CGFloat { edge.isVertical ? display.height : display.width } } -public struct Crossing: Equatable { - public let display: CGRect - /// Position along the edge, 0 at the top or left end, 1 at the other. - public let ratio: Double +/// The saved arrangement: which display (by a stable key), which edge, which stretch. +public struct Arrangement: Codable, Equatable { + public var displayKey: String + public var edge: Edge + public var start: Double + public var end: Double + /// How long the passage is relative to the edge, as set by the size slider. + public var size: Double + + public init(displayKey: String, edge: Edge, start: Double, end: Double, size: Double) { + self.displayKey = displayKey + self.edge = edge + self.start = start + self.end = end + self.size = size + } } /// Screen-edge math in global display coordinates (origin at the top left of the main display, y down). @@ -26,50 +60,125 @@ public enum EdgeGeometry { /// Distance from the edge (px) that counts as touching it: the cursor stops at maxX - 1. static let slop: CGFloat = 1.5 - /// Returns the crossing when the cursor is pushed out of a free edge of the screens toward the device. - public static func crossing(at p: CGPoint, delta: CGVector, placement: Placement, displays: [CGRect]) -> Crossing? { - for d in displays { - switch placement { - case .right: - guard delta.dx > 0, p.x >= d.maxX - slop, p.x < d.maxX + slop, p.y >= d.minY, p.y < d.maxY, - !covered(CGPoint(x: d.maxX + 1, y: p.y), displays) else { continue } - return Crossing(display: d, ratio: Double((p.y - d.minY) / d.height)) - case .left: - guard delta.dx < 0, p.x <= d.minX + slop - 1, p.x >= d.minX - slop, p.y >= d.minY, p.y < d.maxY, - !covered(CGPoint(x: d.minX - 1, y: p.y), displays) else { continue } - return Crossing(display: d, ratio: Double((p.y - d.minY) / d.height)) - case .above: - guard delta.dy < 0, p.y <= d.minY + slop - 1, p.y >= d.minY - slop, p.x >= d.minX, p.x < d.maxX, - !covered(CGPoint(x: p.x, y: d.minY - 1), displays) else { continue } - return Crossing(display: d, ratio: Double((p.x - d.minX) / d.width)) - case .below: - guard delta.dy > 0, p.y >= d.maxY - slop, p.y < d.maxY + slop, p.x >= d.minX, p.x < d.maxX, - !covered(CGPoint(x: p.x, y: d.maxY + 1), displays) else { continue } - return Crossing(display: d, ratio: Double((p.x - d.minX) / d.width)) - } + /// When the cursor is pushed out through the passage, returns the matching position along the + /// device's edge (0...1). + public static func crossing(at p: CGPoint, delta: CGVector, passage: Passage, displays: [CGRect]) -> Double? { + let d = passage.display + let touching: Bool + let outward: Bool + let along: CGFloat + let beyond: CGPoint + switch passage.edge { + case .right: + touching = p.x >= d.maxX - slop && p.x < d.maxX + slop + outward = delta.dx > 0 + along = (p.y - d.minY) / d.height + beyond = CGPoint(x: d.maxX + 1, y: p.y) + case .left: + touching = p.x <= d.minX + slop - 1 && p.x >= d.minX - slop + outward = delta.dx < 0 + along = (p.y - d.minY) / d.height + beyond = CGPoint(x: d.minX - 1, y: p.y) + case .top: + touching = p.y <= d.minY + slop - 1 && p.y >= d.minY - slop + outward = delta.dy < 0 + along = (p.x - d.minX) / d.width + beyond = CGPoint(x: p.x, y: d.minY - 1) + case .bottom: + touching = p.y >= d.maxY - slop && p.y < d.maxY + slop + outward = delta.dy > 0 + along = (p.x - d.minX) / d.width + beyond = CGPoint(x: p.x, y: d.maxY + 1) } - return nil + let r = Double(along) + guard touching, outward, r >= 0, r <= 1, r >= passage.start, r <= passage.end, + passage.end > passage.start, !covered(beyond, displays) else { return nil } + return (r - passage.start) / (passage.end - passage.start) } - /// Where to put the Mac cursor when the pointer comes back: just inside the edge it left through. - public static func returnPoint(display d: CGRect, ratio: Double, placement: Placement, inset: CGFloat = 3) -> CGPoint { - let r = CGFloat(min(max(ratio, 0), 1)) - switch placement { + /// Where to put the Mac cursor when the pointer comes back at `androidRatio` along the device edge: + /// just inside the passage. + public static func returnPoint(passage: Passage, androidRatio: Double, inset: CGFloat = 3) -> CGPoint { + let d = passage.display + let a = min(max(androidRatio, 0), 1) + let r = CGFloat(passage.start + a * (passage.end - passage.start)) + switch passage.edge { case .right: return CGPoint(x: d.maxX - inset, y: min(d.minY + r * d.height, d.maxY - 1)) case .left: return CGPoint(x: d.minX + inset - 1, y: min(d.minY + r * d.height, d.maxY - 1)) - case .above: return CGPoint(x: min(d.minX + r * d.width, d.maxX - 1), y: d.minY + inset - 1) - case .below: return CGPoint(x: min(d.minX + r * d.width, d.maxX - 1), y: d.maxY - inset) + case .top: return CGPoint(x: min(d.minX + r * d.width, d.maxX - 1), y: d.minY + inset - 1) + case .bottom: return CGPoint(x: min(d.minX + r * d.width, d.maxX - 1), y: d.maxY - inset) } } /// Whether the cursor has moved far enough from the edge to cross again (avoids bouncing back). - public static func isClear(of d: CGRect, at p: CGPoint, placement: Placement, distance: CGFloat = 12) -> Bool { - switch placement { + public static func isClear(of passage: Passage, at p: CGPoint, distance: CGFloat = 12) -> Bool { + let d = passage.display + switch passage.edge { case .right: return p.x < d.maxX - distance case .left: return p.x > d.minX + distance - case .above: return p.y > d.minY + distance - case .below: return p.y < d.maxY - distance + case .top: return p.y > d.minY + distance + case .bottom: return p.y < d.maxY - distance + } + } + + /// The stretches of an edge (fractions along it) that don't touch another display. + public static func freeIntervals(of d: CGRect, edge: Edge, displays: [CGRect]) -> [ClosedRange] { + let length = edge.isVertical ? d.height : d.width + var blocked: [ClosedRange] = [] + for o in displays where o != d { + let touches: Bool + let lo: CGFloat + let hi: CGFloat + switch edge { + case .right: touches = abs(o.minX - d.maxX) < 1; lo = o.minY - d.minY; hi = o.maxY - d.minY + case .left: touches = abs(o.maxX - d.minX) < 1; lo = o.minY - d.minY; hi = o.maxY - d.minY + case .top: touches = abs(o.maxY - d.minY) < 1; lo = o.minX - d.minX; hi = o.maxX - d.minX + case .bottom: touches = abs(o.minY - d.maxY) < 1; lo = o.minX - d.minX; hi = o.maxX - d.minX + } + let a = Double(max(lo, 0) / length) + let b = Double(min(hi, length) / length) + if touches, b > a { blocked.append(a...b) } + } + var free: [ClosedRange] = [] + var cursor = 0.0 + for b in blocked.sorted(by: { $0.lowerBound < $1.lowerBound }) { + if b.lowerBound > cursor { free.append(cursor...b.lowerBound) } + cursor = max(cursor, b.upperBound) + } + if cursor < 1 { free.append(cursor...1) } + return free.filter { $0.upperBound - $0.lowerBound > 0.02 } + } + + /// Fits a stretch of `length` (fraction of the edge) centered on `center` into the free interval + /// nearest to `center`. Returns nil when the edge has no free stretch. + public static func snap(center: Double, length: Double, free: [ClosedRange]) -> ClosedRange? { + func distance(_ i: ClosedRange) -> Double { + i.contains(center) ? 0 : min(abs(i.lowerBound - center), abs(i.upperBound - center)) } + guard let interval = free.min(by: { distance($0) < distance($1) }) else { return nil } + let l = min(length, interval.upperBound - interval.lowerBound) + var start = center - l / 2 + start = min(max(start, interval.lowerBound), interval.upperBound - l) + return start...(start + l) + } + + /// The default passage: the whole free part of the outermost display's edge on that side. + public static func defaultPassage(edge: Edge = .right, displays: [CGRect]) -> Passage? { + let ordered: [CGRect] + switch edge { + case .right: ordered = displays.sorted { $0.maxX > $1.maxX } + case .left: ordered = displays.sorted { $0.minX < $1.minX } + case .top: ordered = displays.sorted { $0.minY < $1.minY } + case .bottom: ordered = displays.sorted { $0.maxY > $1.maxY } + } + for d in ordered { + if let i = freeIntervals(of: d, edge: edge, displays: displays).max(by: { + ($0.upperBound - $0.lowerBound) < ($1.upperBound - $1.lowerBound) + }) { + return Passage(display: d, edge: edge, start: i.lowerBound, end: i.upperBound) + } + } + return nil } static func covered(_ p: CGPoint, _ displays: [CGRect]) -> Bool { diff --git a/mac/Sources/DroidBridgeCore/KeyboardLayoutMap.swift b/mac/Sources/DroidBridgeCore/KeyboardLayoutMap.swift new file mode 100644 index 0000000..b3fbc28 --- /dev/null +++ b/mac/Sources/DroidBridgeCore/KeyboardLayoutMap.swift @@ -0,0 +1,32 @@ +/// macOS keyboard layouts to the matching Android keyboard layout (the suffix of +/// "com.android.inputdevices/.../keyboard_layout_"), so dead keys and symbols type the same. +public enum KeyboardLayoutMap { + static let table: [String: String] = [ + "US": "english_us", + "ABC": "english_us", + "USInternational-PC": "english_us_intl", + "ABC-India": "english_india", + "British": "english_uk", + "British-PC": "english_uk", + "Canadian": "english_ca", + "Dvorak": "english_us_dvorak", + "Colemak": "english_us_colemak", + "Brazilian": "brazilian", + "Brazilian-ABNT2": "brazilian", + "Brazilian-Pro": "brazilian", + "Portuguese": "portuguese", + "German": "german", + "French": "french", + "Spanish": "spanish", + "Spanish-ISO": "spanish", + "Italian": "italian", + "Italian-Pro": "italian", + ] + + /// `sourceID` is a Text Input Source id such as "com.apple.keylayout.USInternational-PC". + public static func androidLayout(forInputSource sourceID: String) -> String? { + let prefix = "com.apple.keylayout." + guard sourceID.hasPrefix(prefix) else { return nil } + return table[String(sourceID.dropFirst(prefix.count))] + } +} diff --git a/mac/Sources/DroidBridgeCore/Wire.swift b/mac/Sources/DroidBridgeCore/Wire.swift index ebfb8d2..f938640 100644 --- a/mac/Sources/DroidBridgeCore/Wire.swift +++ b/mac/Sources/DroidBridgeCore/Wire.swift @@ -7,7 +7,7 @@ public enum Wire { public static let maxPayload = 1 << 20 public enum Out: UInt8 { - case hello = 0x01, enter = 0x02, leave = 0x03, mouse = 0x04, keys = 0x05, clipboard = 0x06, ping = 0x07 + case hello = 0x01, enter = 0x02, leave = 0x03, mouse = 0x04, keys = 0x05, clipboard = 0x06, ping = 0x07, layout = 0x08 } public enum In: UInt8 { @@ -51,6 +51,9 @@ public enum Wire { public static func ping() -> Data { frame(.ping) } + /// The Android keyboard layout to use (see KeyboardLayoutMap). + public static func layout(_ name: String) -> Data { frame(.layout, Array(name.utf8)) } + public static func decode(type: UInt8, payload: [UInt8]) -> Message { switch In(rawValue: type) { case .device where payload.count >= 6: diff --git a/mac/Tests/DroidBridgeCoreTests/CoreTests.swift b/mac/Tests/DroidBridgeCoreTests/CoreTests.swift index 12b9a9c..8a9e5d3 100644 --- a/mac/Tests/DroidBridgeCoreTests/CoreTests.swift +++ b/mac/Tests/DroidBridgeCoreTests/CoreTests.swift @@ -35,49 +35,90 @@ final class WireTests: XCTestCase { } final class EdgeTests: XCTestCase { - let main = CGRect(x: 0, y: 0, width: 1512, height: 982) - let external = CGRect(x: 1512, y: -200, width: 1920, height: 1080) + // Levi's setup: a 1080p monitor as the main display, the MacBook to its right and lower. + let external = CGRect(x: 0, y: 0, width: 1920, height: 1080) + let builtin = CGRect(x: 1920, y: 494, width: 1920, height: 1243) + var displays: [CGRect] { [external, builtin] } - func testRightEdgeSingleDisplay() { - let c = EdgeGeometry.crossing(at: CGPoint(x: 1511.5, y: 491), delta: CGVector(dx: 3, dy: 0), placement: .right, displays: [main]) - XCTAssertEqual(c?.display, main) - XCTAssertEqual(c?.ratio ?? 0, 0.5, accuracy: 0.001) + func testFullRightEdge() { + let p = Passage(display: builtin, edge: .right, start: 0, end: 1) + let r = EdgeGeometry.crossing(at: CGPoint(x: 3839.5, y: 494 + 1243 / 2), delta: CGVector(dx: 3, dy: 0), passage: p, displays: displays) + XCTAssertEqual(r ?? -1, 0.5, accuracy: 0.001) } func testNoCrossingWhenMovingInward() { - XCTAssertNil(EdgeGeometry.crossing(at: CGPoint(x: 1511.5, y: 491), delta: CGVector(dx: -3, dy: 0), placement: .right, displays: [main])) + let p = Passage(display: builtin, edge: .right, start: 0, end: 1) + XCTAssertNil(EdgeGeometry.crossing(at: CGPoint(x: 3839.5, y: 900), delta: CGVector(dx: -3, dy: 0), passage: p, displays: displays)) } - func testEdgeShared() { - // The main display's right edge leads to the external one, so it is not free. - XCTAssertNil(EdgeGeometry.crossing(at: CGPoint(x: 1511.5, y: 300), delta: CGVector(dx: 3, dy: 0), placement: .right, displays: [main, external])) - let c = EdgeGeometry.crossing(at: CGPoint(x: 3431.6, y: 340), delta: CGVector(dx: 3, dy: 0), placement: .right, displays: [main, external]) - XCTAssertEqual(c?.display, external) - XCTAssertEqual(c?.ratio ?? 0, 0.5, accuracy: 0.001) + func testOnlyInsideThePassage() { + let p = Passage(display: builtin, edge: .right, start: 0.2, end: 0.6) + XCTAssertNil(EdgeGeometry.crossing(at: CGPoint(x: 3839.5, y: 494 + 0.1 * 1243), delta: CGVector(dx: 2, dy: 0), passage: p, displays: displays)) + let r = EdgeGeometry.crossing(at: CGPoint(x: 3839.5, y: 494 + 0.5 * 1243), delta: CGVector(dx: 2, dy: 0), passage: p, displays: displays) + XCTAssertEqual(r ?? -1, 0.75, accuracy: 0.001) } - func testPartlyFreeEdge() { - // Below the external display's bottom (y 880..982) the main display's right edge is free. - XCTAssertNotNil(EdgeGeometry.crossing(at: CGPoint(x: 1511.5, y: 950), delta: CGVector(dx: 1, dy: 0), placement: .right, displays: [main, external])) + func testReturnPointMapsBackIntoThePassage() { + let p = Passage(display: builtin, edge: .right, start: 0.2, end: 0.6) + let point = EdgeGeometry.returnPoint(passage: p, androidRatio: 0.75) + XCTAssertTrue(builtin.contains(point)) + XCTAssertEqual(point.y, 494 + 0.5 * 1243, accuracy: 0.01) + XCTAssertNil(EdgeGeometry.crossing(at: point, delta: CGVector(dx: 1, dy: 0), passage: p, displays: displays)) + XCTAssertFalse(EdgeGeometry.isClear(of: p, at: point)) } - func testAboveAndBelow() { - XCTAssertNotNil(EdgeGeometry.crossing(at: CGPoint(x: 700, y: 0), delta: CGVector(dx: 0, dy: -2), placement: .above, displays: [main])) - XCTAssertNotNil(EdgeGeometry.crossing(at: CGPoint(x: 700, y: 981.2), delta: CGVector(dx: 0, dy: 2), placement: .below, displays: [main])) - XCTAssertNil(EdgeGeometry.crossing(at: CGPoint(x: 700, y: 500), delta: CGVector(dx: 0, dy: 2), placement: .below, displays: [main])) + func testFreeIntervals() { + // The external display's right edge touches the MacBook from y 494 to 1080. + let free = EdgeGeometry.freeIntervals(of: external, edge: .right, displays: displays) + XCTAssertEqual(free.count, 1) + XCTAssertEqual(free[0].upperBound, 494.0 / 1080, accuracy: 0.001) + // The MacBook's left edge touches the external display from its top to 586 points down. + let left = EdgeGeometry.freeIntervals(of: builtin, edge: .left, displays: displays) + XCTAssertEqual(left.count, 1) + XCTAssertEqual(left[0].lowerBound, 586.0 / 1243, accuracy: 0.001) + XCTAssertEqual(EdgeGeometry.freeIntervals(of: builtin, edge: .right, displays: displays), [0...1]) } - func testReturnPointIsInsideAndClear() { - let p = EdgeGeometry.returnPoint(display: main, ratio: 0.25, placement: .right) - XCTAssertTrue(main.contains(p)) - XCTAssertEqual(p.y, 245.5, accuracy: 0.01) - XCTAssertNil(EdgeGeometry.crossing(at: p, delta: CGVector(dx: 1, dy: 0), placement: .right, displays: [main])) - XCTAssertFalse(EdgeGeometry.isClear(of: main, at: p, placement: .right)) + func testSharedEdgeNeverCrosses() { + let p = Passage(display: external, edge: .right, start: 0, end: 1) + XCTAssertNil(EdgeGeometry.crossing(at: CGPoint(x: 1919.5, y: 800), delta: CGVector(dx: 2, dy: 0), passage: p, displays: displays)) + XCTAssertNotNil(EdgeGeometry.crossing(at: CGPoint(x: 1919.5, y: 200), delta: CGVector(dx: 2, dy: 0), passage: p, displays: displays)) } - func testPlacementSides() { - XCTAssertEqual(Placement.right.androidSide, .left) - XCTAssertEqual(Placement.below.androidSide, .top) + func testSnapClampsIntoFreeStretch() { + let s = EdgeGeometry.snap(center: 0.4, length: 0.6, free: [0...0.457]) + XCTAssertEqual(s?.lowerBound ?? -1, 0, accuracy: 0.001) + XCTAssertEqual(s?.upperBound ?? -1, 0.457, accuracy: 0.001) + let t = EdgeGeometry.snap(center: 0.9, length: 0.4, free: [0...1]) + XCTAssertEqual(t?.lowerBound ?? -1, 0.6, accuracy: 0.001) + XCTAssertNil(EdgeGeometry.snap(center: 0.5, length: 0.5, free: [])) + } + + func testDefaultPassage() { + let p = EdgeGeometry.defaultPassage(edge: .right, displays: displays) + XCTAssertEqual(p, Passage(display: builtin, edge: .right, start: 0, end: 1)) + XCTAssertEqual(EdgeGeometry.defaultPassage(edge: .top, displays: displays)?.display, external) + } + + func testTopAndBottom() { + let top = Passage(display: external, edge: .top, start: 0, end: 1) + XCTAssertNotNil(EdgeGeometry.crossing(at: CGPoint(x: 700, y: 0), delta: CGVector(dx: 0, dy: -2), passage: top, displays: displays)) + let bottom = Passage(display: external, edge: .bottom, start: 0, end: 1) + XCTAssertNotNil(EdgeGeometry.crossing(at: CGPoint(x: 700, y: 1079.2), delta: CGVector(dx: 0, dy: 2), passage: bottom, displays: displays)) + } + + func testAndroidSides() { + XCTAssertEqual(Edge.right.androidSide, .left) + XCTAssertEqual(Edge.bottom.androidSide, .top) + } +} + +final class LayoutMapTests: XCTestCase { + func testKnownLayouts() { + XCTAssertEqual(KeyboardLayoutMap.androidLayout(forInputSource: "com.apple.keylayout.USInternational-PC"), "english_us_intl") + XCTAssertEqual(KeyboardLayoutMap.androidLayout(forInputSource: "com.apple.keylayout.Brazilian-ABNT2"), "brazilian") + XCTAssertNil(KeyboardLayoutMap.androidLayout(forInputSource: "com.apple.keylayout.Klingon")) + XCTAssertNil(KeyboardLayoutMap.androidLayout(forInputSource: "com.apple.inputmethod.Kotoeri")) } } From 8ff706c626e835e0025fd488010f11776da53034 Mon Sep 17 00:00:00 2001 From: beyondlevi Date: Wed, 7 Oct 2026 10:56:03 -0300 Subject: [PATCH 09/11] mac: disambiguate Edge from SwiftUI.Edge Co-Authored-By: Volund OS <287815646+volundos[bot]@users.noreply.github.com> --- mac/Sources/DroidBridge/ArrangementWindow.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mac/Sources/DroidBridge/ArrangementWindow.swift b/mac/Sources/DroidBridge/ArrangementWindow.swift index 6d88f3f..3838279 100644 --- a/mac/Sources/DroidBridge/ArrangementWindow.swift +++ b/mac/Sources/DroidBridge/ArrangementWindow.swift @@ -52,7 +52,7 @@ final class ArrangementModel: ObservableObject { var best: (Arrangement, CGFloat)? for d in displays { let r = d.bounds - for edge in Edge.allCases { + for edge in DroidBridgeCore.Edge.allCases { let gap: CGFloat let center: CGFloat switch edge { From ae4f1cda08cc86e1938673720b5182514cecea35 Mon Sep 17 00:00:00 2001 From: beyondlevi Date: Wed, 7 Oct 2026 11:13:02 -0300 Subject: [PATCH 10/11] brand: app icon, menu-bar glyph, README logo; README covers Arrange Screens and layouts Co-Authored-By: Volund OS <287815646+volundos[bot]@users.noreply.github.com> --- README.md | 11 +++++++---- assets/logo.png | Bin 0 -> 131718 bytes mac/Resources/AppIcon-1024.png | Bin 0 -> 628985 bytes mac/Resources/MenuBarIcon.png | Bin 0 -> 453 bytes mac/Resources/MenuBarIcon@2x.png | Bin 0 -> 945 bytes mac/Sources/DroidBridge/AppDelegate.swift | 20 +++++++++++++------- mac/scripts/build-app.sh | 10 ++++++++++ 7 files changed, 30 insertions(+), 11 deletions(-) create mode 100644 assets/logo.png create mode 100644 mac/Resources/AppIcon-1024.png create mode 100644 mac/Resources/MenuBarIcon.png create mode 100644 mac/Resources/MenuBarIcon@2x.png diff --git a/README.md b/README.md index 26156c1..d6f82f8 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +

DroidBridge

+ # DroidBridge Use your Mac's mouse and keyboard on an Android phone or tablet. Push the pointer past the edge @@ -24,16 +26,17 @@ Status: early (0.1). USB only for now; Wi-Fi and file drag-and-drop are planned. 1. Connect the device by USB and accept the debugging prompt on it. 2. Open DroidBridge. It lives in the menu bar. 3. Allow it in System Settings > Privacy & Security > Accessibility. -4. In the menu, pick where the device sits: right, left, above or below the Mac. -5. Push the pointer past that edge. +4. In the menu, open **Arrange Screens…** and drag the Android device against the edge of a display. + The pointer passes only through the highlighted stretch; the size slider makes it longer or shorter. +5. Push the pointer through that stretch. Back to the Mac: push the pointer against the device edge facing the Mac, or press **⌃⌥⌘B**. By default Command acts as Ctrl on Android, so ⌘C / ⌘V / ⌘A work as expected (Control becomes Meta). Turn it off in the menu to keep Command as Meta. -To type accents and symbols as on your Mac layout, set the same layout for the -"droidbridge keyboard" in Android's Settings > Physical keyboard. +The Android keyboard layout follows the Mac's (for example US International - PC becomes +"English (US), International style"), so dead keys and accents type the same. This needs Android 15 or later. ## Build diff --git a/assets/logo.png b/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..2771e387bc154b5cd1ce4b86cf06e29012ce9ddd GIT binary patch literal 131718 zcmeFX)mz-%5;X_}cXtmKXtc55uEB!4Yoo#4-Q696ySuwXa0?LJ-C=mooV@3|nCF>4 zVCJSTx_{lfchy=|Yp>d&^0MMc@c8gxU|>j+5+dKhz#yx^z`$c*p+DZ~Y)I7s0|x_> z6cJQ%Svl!&Zl_Rn-FxRLDz!z>)=^3jO~t|9=erKZpYq+F#e_^QV$UH4u z?^4D3-`9eX!o#ig7lmr7LAgG>Awo>PqM#_K#slJChG>7E>U18{CF}Pzu$!T`T<_b8 zMm`R0FB+f+MtiQ|f%X=jmo2nfEtq~BW_vQvX@jo2Pr{ zKeTbB`SH&?j5qhQpL^QCA8ybP+OWg6da*wxw|BDs-8Tvpg&v%p4dj-^P&M-6#Pa(~ z1W*leGF=<^Oao(mS&cFAY>1c=3^d(?y$(>%bHt0Vtm674;OC?MV*f*s{e5@XRu{~N z3w!-*^%K`Hj(F3D^90jp=+mTa3)w1g%R9;7v`*w^`Y$N z)gt-$Q(CB%ldV^W)Dsoz@4}ACyj>Y|Q&$^NqKCgDs#p5jgYUuge4y-UKTU_KwWQ4J z_?Gx!i2@)LP7Iz2tvpHL@CEV9N!7TjsbJT1qD!Z6Y;CIU7HwYoWOk!iW)W^l4eF|b)ewl5B%o8zt?~9EUz0sOG3-G5T|Fl-g2z=) z?XKms!E>*dFIDfD(@EG)>PCMT?#~DouhPKs#Z52oNz-jNs~xz0Dtin42fPH0K3>* z2^CD#_q2A;cP<{*a^tFCwMniXX{}}g*8n%!>%r{i1D}1?H2$0RoO++V#FmpXQbi^DROBV-@LGw@i=_7~$ySg264FqDRZAxx0PKV@$J1^QzHb8+D<%=L zE}LA8PW}DuRa^psdoXr-o>J8-9~M2ISLygYU+p()9*feN=Qi7#h{`ci}ZNvDUi z;|2^))|vEkU1{k!o;+$`S>#Ptd1R&aj{+{m+dbX7!5!KgWRWsFJPDw>sa$L7CQ5nNRmSH!DhDbu%>3$J2Y8Bp>X|NN8kq?rLfffB zW3%K@;|Ctw5WMzKH+EdAVq50o{!lwE|EmUS@IOinvYbO0j;BD+-BfSQv^?&6FO{dXMiY)Ssve|{dS!((Vug9gxh)C>BinBX3OJ>##kET z#;a%?mj5JLU?Z;5*R5N;nLmAI7ma47EOSTL19t)XFL&1zw7pGgo^3d-12-;>S>a5t z`q2Li|C-Vmm?X}VC8ym+bi##woyUxQ%sLk@-cnVGgiGWZC0^fvSSBvnm()yZEOWDj z+~3iO8}xUSkou9YTow05hlT`prPq;D2v~+d5m0Sq-qm~yh|GT9;um`ivWOrTx}&y! zBj;s^41w3D0D43m3JZk)rdrL>tcDhuQDfb7%vQhpPQQ5;1LxU}7osBPoM#2Dar-+h zOl-~G)Dn&OX_rYyYtKgbPs#vNfx1vYl@>eUg z*o6VR5|aq^$}gHma_qQCpNXBk6SiH>h)g(-2e?P|NWHzZ$$)iV)?FVf4b|bZJoRUc zs1A2A=ukk2ssu94gDakdO#_mp=U4t`x(^}&5bq_Hu>Tk34{Xsjo~#*w9cW!bVADwA z&$0Wdw{=d}MU!d)zz|&(2RpaizFqjG1`oVLb(N~{*GlS-AzqkeRnZBaAP21r8l%*2 zkU0-6Vxj#Yr;C_K+~k#G-r%_MdC#q5k7`78Mg%S84MP7VQW-S;_kpcG^0nF7IPcy> z?#A90>c`kBM1Vg$CQD_C6rcK|j_F!f{S8POPS`|gUq8{1nGs&`N8N!CN1sT$#k=ur zM>R$<;M@xq1jR-ng*Q#9Q`oDr+B4ncY5)yExCj+Rv(UT`(cM_a%Klf?sPUS`(jpUJ zH?l+Tdda_5CoY+pu*t_0J5; z6#N9THhq5BD>8Q(hO44u`9-H;8!f(%Z*xf0_FTBE#@gLb7)W6zcFz0T@>#NtA!E}M z%VQgZ@o&T8DK_(^MMlBrrr|!e$!f8CyJ0&ogJ0e5s6xP1_ZR#Iuc18rgN5tIsIh?r z&piE1^@)HgmSJ#j=<`6j<&&{FufKB+Fh!1-B!E`Bw!HN@c;6sd!UQ^n1L7iTBi$6- zIh~+N>+bT@<{9w8TQ%ILFKi)yTS)&sma$%FUw|ltfGgru$%y#E9%z4}$KR|jqZRNo zCN$ARhmuzI$KuJIe6eYyotp;}lFt-NC0MV>=?u=lXYpD&XkV{lX+5&^|UcysB;dJr>t3+%Wu`P+zA`ksykL zU!3SBP5081d;?>DVBNNY5J~^-Hz6=Ya39rZ3vI}=9WZKu z3QRE|PdXokrN6C^8ega8`lNAFHR`~0{1r5iEScrsbH?kUS6JmQD-R!SaejW#edk5i z7j{tFtGCcz@nA!p+%mpW=9pu8P1gVHnlsY9?ZK=$4i1Lxu<@6iE*JmwH%45-lG*et z?;@AneQbF_u;&dDNd)jV}{B5j{A0uBuOE)ZEvPgJAMNW%nnfo29+@Lrn(xUP5KyT0{+D~(g()3psxSqF*JoS zU&6x*eSh!F9Q`FJh;3E%y^JRO-kP$6{gk$&Bb(khlw}uvW-QKuA>Dv6dtId403?#` zalBBKcyfeoc$xbJ>fcs+Bt)#XeL-5R#5Vc~zhbD(>78Wl7k$P4C)AfZRGB&d2aL?B zn4ij&lCm(C5=(0ttHb`|i0gfG?2Rhp*2l>RvT4mtsj3}G!3%Ci%qD4gKE9K9{{{s5 zqnHNs3Cd}x*^23k@}93 zFnM;e5|tOZ81w09!-(WfY}c|chOcjy^CG)IURR3ElGER==fTJ?u-M@IH#jvG^3X|~ zmYmKzVlXgi>O9xNz)sf4k(E1d3eD(JhV;_QE(@M&SpQTT#2NpEUmIQK--o|BTj>0x z!>FIGU@{UIuw*dG!$w<$zRTF-maF&^n;EP^_nqy~P{N}Na9F&=hr%7rIE`qS$@p(N zP(IS3-}kn&Ips3h)EX(eQ}(CG#%q3lku6KPe$pt@)88?iPt^g8XiI{#ntnoh{FlGL z-pZN(mQT4WukdGU*9RBGZzvoJ<>RfjqNV}<{?cctyDbmiNP-#pfeFT zvW68Yk|90rsb91kT>gwK-qv8aXR$Gq|NZT4LGgb!kg{CA@mo*kiwqn; z4SEVb&>{SLR1W~l{pme=DU~Tq{B%xJE-Q^3kJ5X*uGtR^%Ub^`xt>h_;QNv{K4`csyd=&nC2)5ff zRE>|g9?b-IY~Ziy(|`1j{$w%$l_}3l!evpaD-tb~b%pn$(wmqJSo@v68{;Wd0}$X3 z>D0a(EE#SyXS@WSzT$(^+t{asS>(1E++0Y#8_!u*@57i}?12dz%clO@t5yD?xjxiZ zN&z9bSzMffcu#^#I6P(0VK%|c=w}UcDCTe(96@wRG37fN(l5H>QtvB(Dzm0Qib8~O zcqD3+d4o7y4F1XzMk1T@bWj81cyHa!$4UIZMYkMZNr(oS;A}8*l1Iz>=j6B5n*ODZ zHt~Dd^kzAeVB*ZJe3yZblMbIQND7M<=UGxEW4P5~6eZosw}z1%EL;XN)4t}0IRWV> z5&RC(mO+W(_~d9*515;O)rXz*_dvk*j{p$MfHrWIlH)hxV9R`>dQaJxV{>Lf$U`?Y z*S)7O!zlK+dO11k`;X*lD-0jag)Z+^4{FgkF~z(C$=Gz@g+YT(>v6E3 z7hXA3!gzfrZAY}ow1F+0^%J3hy+~Oh9-^g<6DbILV(#RZ>*v3$==p~g<2h)SwK*CM z!3zyohz7DhNgF!Pv8B&^%6GF1N7P!1F>NFp%0{n;w=f%9Hr?L2}uY zx{qf}DJd>&t(tWGsm4#KXdU_gT8_jXM%xU?1lBsv$weIL@{Z$R*-j6~kKfe47mDvi zBOJ|(oaQzdyIQFV)orZDb-AP15UXpbJaZfqE)s-0p%+MJl}sQ@Z*RwP3~<|IgD%f6X;Kn!?>2>hlw-S-H{RPq7U=#9B<=sKpm}du zk(p~!`hr@K%cZG#FGdQ_kI{Kx;v;8NEH8FI&zxZ)T0%-L$~@M9r<7Sd52p}efGar- zwOK)Ged|E!hKsm?NDMtyA4q<+BTU14Qou0V)OmXNsdWeM7wui%(hC}y%7}BpF z0ST~M3RvkQlkc+-{`^!AFFURbul*=-$5QN8=;x_hLKAZPZFMHZ&}j9P5pBRgHc5TF zG_1-ngk4ENLRfJS7o#JhB3;$}20Oa*wn++o6d*~T@8}(VUexA%5EbGpOF-6ND!i_s zIuucQF8EJ*M}hx`@b!O|cg>5r-AgCOM$IxET`gRQp?+41R6W)#K#cXZpOZgR7>;Tv zI;SU%Cxq#jto)kmY29fW7ugtYTwzQgZ9SMXKGE8(YssnFQH2n-|HF zkoA)VvjlW$_{?1QJw4--R^;0I8v)h3 z-~~I-Hce~o02N&+u=OgbSy@ytydyE-R;H@AudOoVfTa`Q>RxlgY@T8!nJh|LA&Dsz zB5aehD4sK=^q*~gNIgnqj#e?BlOJh*9Ll9ztlpA|Szo5&aI5#pI(P3+HS>dKI}Wr~Q! z6kfJ329EI2Y~)Gm8!Svlvg#TnO9`hlPgW>+P;@xgMY9xNj-zI#9qAD_lurrrZ{4X` zT&uuVEW}x0UB;s*I~RrU{^y?kr>8?HiWXCz_7AZt(_#|Tvgt(oDaCbQHGDbFBAK%< zEvJ_}sjqGX#D+$@r2zt8TJ#Tg^&6@%zt;-g3p;t&HF`0Puz@y(XzPu=f!*Ma75|f< zWzhc-yt(AK`(fxdd{1X^>xv-3{*M6341bd5xiK$H2@K@D5zz18 zcY<AQDFig$odMlditXEPoDwVTL@J^oWI8Ew`Mz>t?P%pyvr zl*zRmC1tdSx(;%zwTA>9mX!;jkm%6Dd!rkOVCFOg*pqXK4!(cWZBC{%26KzjUYJ4@ z?h4mennWj|ub0A)1dH5L=Q93Z!rA^qe-4ta@0gS@H9LBmm5Z=SJpsH6n=-CJN~K2< z=lBDa<2mawQgYv3iptqy94^e=7WR7c)G+ZU^<%+U;#V*tW5bx@%w z?u2Vp=^mcbQTyMq%jNISAHrwJ_6Dwr)7ENLH`;1LdF2=dOe?M?AxwVVyAkEwdJMhE zfL?#!De+O6nUC)!vxm&9)I_X+7ozCM;`G!KSmvkbxjhwi5!KV^G2^kg$OLb#4VTOE zzurRi4;=^wuf^H19Ifzj9GArf#e_7zEgfoFjcC)1NKdTCQfM{2sZ5+}>tJa&&t8p} zEG42MXJV+p_k2lJdVL0iJoLkH|ADx^>Hk{{)hbs{)n}=*i5(%*WP=v&|AsJ%=pQHA z08S%?4>8=%WSCIAbc=BEGC84FF*tWW3IVns$LF`u(o{F5XAxJ@3ge!w-s;?WVYS#p zsx&ZOX?C>%r?3ue-fJ*a*LN@ru9K~kvbNrI!l`2I#miF?|8pM*jvGxpDwy8?^`AOI z*f>-DF;MBx4P*J|V3Jw5>n<=I2e?#jQsJmI3lViOluykwRj~z$-^_-o980og8x|o` zR&nws!Q$yktT)Du{qmT~F5`u^xv0p-)?{S7%i_ZN12At*c{Ih_s%NDBZR;^ALM2yM+09p)1N`*!!=SLFP9@(rV zQ3>atwaU4Spgsng!OZB@F~=}VHNUGbNITvdG!eOzEy+E_d3+R5DrC%s60#4dk$&MR z7u?xtQz@!$w`ValQUA=pOU5(gp+q1PoA5=)W2`k=* z-v@J`uFxaGS6~~+;5U*_?PZ!D#zu7!YAed(4MMEKfoxM@Fl35yW_3BP zX9sbkhp^;lU`H*HSOsSvr6~8gL31JrYYGK7HGSQ#09fz zJ=8{w$YR?6VpxbLgir@!ft;{TY9-Yny;oc{Ax- zIU-R5E#_zmuXl%cs&lPK;ala$#7)E_XKxRd68?%}xFSDD^* zli=+*=)Iv<)wY+;9n=I76LYIw`{TGXh1NLN0vQ^iQ?(@(OGvXvPZUe)uAF{3oE$sG z$cU2C>(VY1MWqzL=9Wwb|6OvPB*j!|K-gl6pC!J*K(*(B3R%y8;Noq`#4r`4f$KCi z*BS8be-(`w83#OEZB2!>cix)+dUR~E8mW%|>klm^)hu%(K~Q{|&q}EUW&?zViTy7) z46u3_1zbIoZ2PK0GJ}XA{QukK9=80U-J^09q=*-8bi3;S^j! zqtdCmp%q<^irNo}_HrR}Qm(DA5@rwbO13nmQX?i=(%)YyfKb6~aK^F!RyK(V@cu>)i7pt{Sqx3d3=1BLEiw`3EQR4~W-Lz0>aHF3Tn)}*25v6eHvklWqcH#_?^ zpX8;e!SPYN=@i#%$wzE>WhO}gdZ5=wyHEQp#V~41X4R&aXpIbo-Y8rC0fJlc?$6$X zXwf=2F!}B$1Z%^D)WCT07Y)?P)@QXeyhJ|c5guuT3VxLz60Z=#gLSBxv^ce6KS~lz z9>;E@I6VXR9By*vxlI28u^gXx1!AFB$hS}*DTn5LOwkL@exT|Zo!Y?Fh^aEArv)O= zS5!i&h6V!6tgT*#20d4c4xX$6BUbt)K850$3BJmFJt9E!Ja+x!{nLS=b$Nwpu% z+Udg2GmzKjHJj&IF1oIv>o$4GbgPU&bvg~akaH}-F<32k(ID7Ua9(;#i!s0e}6y1 zn@aJcK}R*BRf*O|U3JndG-hyZwQObrz}x1=rA8ea=xifXo1<*}Y{bnb@pl~Sa^ZRa zug33u2IkE|IMsOvBU`q()s_FJ&Qc3c%nM2cZiN)9yqAT~&Cy@`p5s^qvKotJ$uZ>^ z0Y*EyiS%2+Q(}YmpSYJ)rF{t&+q9vDD4+Zu%lA<;#7d$mM4g-486r8}vwN?H@I?Jtj zFW;Dgr%u_c=0aJwM9GX*@WbEPgsY6Q$pkB&H#WRv++=;(j5tSmv>RQBFb&YFKLt@U z{pNKVIdRcwTBkpT-8cK);w#PBxoMl@R%c=githu_8cAhGY_}6*m)xa%m|#t_gyp)h zmiy-#Q+wbWpnh=#`*GE45jTVgVUF8uGu!S!U`M4{8=FC>+zHwe$?KdB~RY z?%BzD;ux}8Yri~8e05$V^gjE!YU4hY+~u{-|JtwU{g4l)qLJDQ{(CZ$`;?5)q(S$z zw=%G2QMSPwuci~ykbqklO=cNy(l@VSAH;tv76Y%_-+zxE_I|~#P_l*>f*%(HlNI zTvjjB5SL?meHQ})2Y*>VP$IBTDkL1l%Ct`4n7{-M{I>*$Bssh4y9 zT}9jei(mBocC#}>R^>^mcSK(~IBcy?Td1u$pVolU+`BlPt~<9R?VjiBHqD^jO6Ry2 z3uiMGEvO6HZpK)YWxcn-wnfe*uei?3HPdY~Vmi)_hQXsuLH8J1`sWIaxbD3SNvZ2N zkybTmvm|#}vTIL{hn)Dp);YDxG(N}g9A-&-k&aVV>#jx;T$HkZ2$_!b1hQUXBG|a# z$&isgW;lq!Y(W#`c3yU?lAj;w_iS4~MNzH$G;HePbt))5oILD#>h{i_ar>-yGhnLu zIQ=>=OS!2XoBSc_siNq7j?kT(YHPvDO?g9#G^wfZX`(a}`35S(a7&OAm#2aqAO5M= zzhPb!Jc))d!mE*R*)*xj1a35NkOITmSP5g;un?0hHEPieUeENc|5|tZdZz}vZKsuZ5zva60ssmuTI}A0vrpO0A)xakS_aoSUzQ>Obyh6gtVn z8x|u-VZh))3Fm^HoqZ6|-&)58ig-$-f zcrVHTP6}*>@bLcG?~yo`>2$<8@2Ki;2O-Td1Ha8?_aY{KCKmZ$-;kcs>@9jiscJnD zH+jErMmuX=*L;J-m|Jf6{c+O@XY!q(JoS|=;s~oc!yG#VOrgMh5O%p!#>Xh&Ym7`k z7FE`uUF^cJUQ{Qk8bR3Yb@#>*BbWymcN13knUt;%XTgVLTj;JHw9%?<;5679+A1?$ z?#_hob2~rScI!hVCd50r+YTmvbLG26vnlDcTYT@yMy!&HKe)}^*x5g2B3$rddQg~b z-DIt6`>HreFwQnc9S6xfHkpLGaB#H81&q`SLRfV5&5?k(o3)=<>z-2TLe@j6o&ay7 z)bnhzZx+$8Q$tV-w^hSmsC&O1DO$AO)lGRsx|qkMl-8Jarf zYXXDjOJzji$I2l|f zdEYvndKfj@wwZ2srGz15_bPL4~j|XRJ zQ+M4+8yZ*`ORjt$_6QqM>X8oH(yIG|`1uEi^PaaUH8~k~)}B*cJ8O{Y!<+#8_S3x1}J|_d9AWZsv8%^Oxj?aj<0coq1&b-$QWHtVh27)zsaa>Z{pYB z8Nx&yt@yL~*wsSj;=u;zD`2N9G*crJH*T5Q4PbbK#_wNUn>rPKPIbt^&DY*TO)OR> z!hqPbtuWGn=Flj9WWGIk+p_jr0qO=8;lC9$?fT;qi}+4qEj8#10K^K|DsI{sQHTd-XCd`IZ z5^&>nW&pHud~iPnicf@xY2#&suzX)>QMw7jYFd7HTM;gw@OvKT%PrO29}f^pmjllC zEx&drciuR@je9>Bzj=3EZ?1_~zDY0>s}2mx5YPb$l=ElI+IhT|;L@Yqd-|gJcWt~c zslA>?<-Dz@-k&bm7I_{E5ExRZ)fr)q?@zB$-P0j-j%t%WkvK)$4_l zt>V3TG=UF9nhz{_F-Br3O(|A5ohrahul*z{o(6+NV|<)(y$ufjQ&;TqHSSx$WZ7wn zX(oq?n^*8EeQcTE!+e(Nw`20mnPNB%*qYISGW#haM4gUTXU?4s$Q4nQc{=t|af+cR zhe>(Py4lo3ORtPiNXoo}S7v|8o+QRwaInM?H-Ij?yZZ)1)q|CRLL?WEBYY89G=zFI zU~~dpT-X%w_=JMu`S~q>y0*s4!OC;|Kg-7e4#NB#pbp?$Z3z%K_XUu_z%=j87|?^E z!&jwF1bAI;<0pwN*KZgmw>e&#S3+L%v6Q?JQ@*C;_xTt-??`cR;UgL9KJH$IakvLe zAitHrR8YUO>3AH90eTEC$!c|;bx;u(5mM+f>;dpx`-BQHwl@nAGC_VDVU5g5KvqNa z2&h9s?FW6qeZmnxJ;7QZVZmUCsD9xV@HABP>dvY<=pIU4>$G=D!%IZXN-@YW8kX)R z{*Wwj?0%YBw0aNJleS4q;(+^5SEnKdQt6<>U{`kfy>xVc@tJP2d0c=OTZC>brI^GX z_%(DH4^!#Zy{ZFUx2jE>*9+bGyYR;0y@r0jw)1-6KF>4W($hhO$9mpaYD z=ybgf*+bRtsf=61_#Ap>=h%!N^eaIeTJ4WivT*G1WKLJZ>~DZcnTdPh!7mM|{y``y zVB+PKgQ8`pF8$7wc>qkc745|ERfYi{8;(BLMw@!XbGSUq$%8#e(;Whs05&muve*I; zkG1D+^3a&d@1jv8|3yQ))tShU1{$>EEbRKq3z-Y6i1cn~T2@O*b0`c<)MEW5}Y0N`1okSs&Uz153KWFKBcbGuJhDVZrboTo*?P8Ki}|v z+)n0uw~liMi%7c%wI7w1g_Tvdj*`G3c4{-FtP&yA!g; zwT}~!r409}{`H!4O7%LGsM-yBG z&^+a`kAaKp={<{&r4VW9OtyAaU^l%oIWeq9Qo2GD!JdOZscoZ2Mx--kYR?MbngWsp zjMCd)skGEN?7(`YKa3La<2FP6T(C2jz^{Y@@MF-r-B5L0+%9-vwDJPQ>Yaln?YO{V&E zrGh!y zPJaLQNZrnxFH1LXnxy{ABckLTv7#J9v6A)~)qF$IHGfAX!SU`A+%REsRoLR4w>E^R0!ooq+H|~Z)s`XPL{scUeQ0AT$MyDl7Zupg( zc}sE@2NmIQNEi*qGl?ei2p{SRwicFpqYZnRPF{T)3*kelHRa7v{>O36%OW{1w7X~K zu$DOfo0FoQqcZ+kqlH-kuJNkWzE%Syi*vhE(=H!5uOkgY*FXHfW23p^addwO+HJJ# zZ#VC-r>9okoplh*yEy#$i6qYRIDAWx<#82SJQuTN<^ygWw4uT7*UdET) zkQOtZP|%Rv@d zlUp(!e0&*{_CvfAAM#*p3#ph;!POQ~!=5;eu6(2fKzPuOmrni}24#~fyw28g)Uad5 z9~TW5t=PZ00fiI2IqD|ipW>bjHFQvliqm6)(wB-m~MiKeltkiyn1xRUj4v6 z)LcTW%3%;eV7I7h15vdJlQU?$bf_I3j_j_Xi`%e_9gGle+Z{L2e&Pr8IMgwA)Shc0 zM|5$DJ`#8vG8)V2D1hT6$ho4+Ajr@rH0)C7g6OK841m+*2!`t)dI(@f4}1Q>Dve1Z zYm_|nl}48PRX$YTtTF+t0xwzQ@~ zc!C?Eg$MOQp0sMy>~CuNYwx{IY^+YOItzESztDSJFI4H7AIcVF&waS1^c?6*XLhhg1N;f6Mng>B2sydBFF;s&cNpb~8XEz`@mw+#9tYgZq>A#NK?yx;uItj4ofpBu~Y0p@@U-&~( z^_G9*dvmy;cbZ8NkNx1?c$q-i;@@~0dhmaB>7ECZh3q+=^fNqIjT+lQ?2d@;x&zc`Z{8sGIT!i4)iKQ~?;DcXzR_ zH=?)?mv*vT*O=3@U7t#dJdUBby{^nwTc zlT=exwgT*S!>4VMDju1L;xSRU<-JSAY}Z7t8S<01Jsz58BjjzQ-&6qP*@Rh-Yn>mY842@TY>srakFEGgvr<~&G@G4a(f{y8Lm%{K10RUY|VGf zb>C=+e}&wd6w%i~OgrKIxFA7xXo6x>ZQ|4Q9~=$#8)$}ITzQ=Py=t)z!T*9^-Q3}sjmjDny z`gNkw?zQQ47EYI5-J_g1`mJH3?OySrLw`>6!*Y*AhO6F`CJgGsf4^MYVeZSW8?`_; zHZ+~rR8;d`p3Zu%|7q>3Z~s-%XVtcETX4n&Cgky8O7lIo<1 zaX)uunaAR!pxY@(8-M+2tNS98m8!jhaIeaZGM!I@ zu*TO)RMmC%$>rtIRPTNLx#n&diSxbp`tnfjyl3r2YZa{uL-?pQ3eVL38t60e`@^{C zZrgaYZhy|+^jDPoI>OL?;i2U*19i5A!cPQRm|UdGx9ecRw`;`BuzixGeqTChHuOHB zL~%dd_qsIozT{o>etueff9Q?&PWjVmlB?GaPg4o;GV4!aV#W3LVB$Vc>4YKZ2_NQ1 zsX%FIVJ%$+ff=8sAfb?@AXMS6os)SGTk4rS+aOdy^m>h^+Gngp%@PxbKXAJcI@$t&|X#-XlbP)bcedq+ftX;r?F$FbwW=(QtrVTw86uLWb^@xih&}KvVUIvDGp7wF7dHVfk;9$;9aENnV`Y9EJtUCwTAuk?h~YU7nV7 zZI@Stj05}TEKUg(&puVcjb8z$cW1?H{II|EFB&0oPM$LhB_A$XFSWOK~LMH=}tr|9&v^ggZ{`~%;S z1tsa@lz0-DmbBAQ(!X)D|MEp10K5rI|j)UN1 z$ei5JOPXs}ZSD8yAuLl<6B5VKr+5;_lb;j(_7iL*uZ!nvZpWH@_sdgV6>Q$oUh1l+ zYln^JYwF%4ueBi{@T?RM>eLY%RQ45K)jrI*`9nV?uFIOHvNR{=apWLZZKzmWB;b^8 zR-7Z>Kwo=VXoRckp&zNX)=f6R5!0&E4a|Su{S-$OE;z3O(&@DUmR=ocz$?;~YRA`k zi;J;lL8z+5USFRVoHIPx^XSf;CJj_-);Y#B+aKHG<}VOSD4|~x-<6tca5`autHySO z%`AN0sL@klUFm}ClW6ezqWa-|KQRs6CR)G0t7< zx7qoU+30-J`jpS{g5ZRx3MNlkgTmy_&S4rwm%a4u;nt``kAH&{dHN;ibrK}bx!J`8 z`w#}KOJ94sbOqz{*G7;v4(I^Qtw8gK3T!-dvnPiv8}Y-~{#ahHV%P_F{_a$49rGcU zxt8=S4leEYkw;Uv(RDTFyh8+@-#eD`@dj9)kINlMT{Ik5eFX(Z%No&d`A#8%+kL-L ziOskH)wcPqw4GaB6uS`g&w0DSt}}0D3FB?vbo^=bijgWadT`EO@cAB2c^`Tdr}{QL zpKWJDskc)MId)bzU)W9d_w+V~Ol0hu$j{&!u#r1Q#?#WX`(9SqE=(R?aJm9O{P$nm zw@5Z4#QBe6n{Q^rUC%HkzsT|K8exhmoUb-bJ-0GMt9k#SB#2&f7%`{Lx;fT;x=QBv z7+QDk*gZn=zTvHSY#W+I9|t7hdR0w`Th0AEPIt&;|CxF5HC!rb>^j6Gh=zEo4KZX*w@%Y28nN*R$=*|G^QlwQI_MDk#a>P>JW|ctm}o;nWiB1NmtYP zBP`x_M)~HAyM}1AaGB;UqZai8DWNu$b~xKB$9O1yAk}g98f+ELDMhAiokNR*mi{kG z>IKuiies0Rg2V=MUDm~R(TCRU;pawr^PW5x3LkNe!#9}@r!($dP?PTN;N8>Ay8AmG zcaSryp|PexabP%S{{Tko6;Bd{IM#tdDV?QM9a3H@9gr+dzpgaZv3&XZ&}m)so~sPW z;cUj`(lx!ho&tCeH#_K>#Bh`H%pY{grs#-%exx#NS6%-Fo?Ej*i?hvJ(F?K8Pf&Gp zsy@Bnca5|nokO@BXWveGKl-dNVh-K3b;;+%LlKB^?!ecyfjCf!(AdI>h9~J~Wu@QT za>w@JUf)EW;U)|qG+UnyK__QF>05GN$_7Pom%7 zri4zcZL(HUuSA?R`a(X5ZO?@Bm``G`Czs*xgFQ%lzRc|!PY+a4ABXEDxsKMVI>w@0 z#KR%06ZD#_lYdOM9J_YV{i>G-dP*E8=pz?@#NWiA@PA7SX%IV*fXricIV=>gliG`* zG-pH%^P<(F)P}X{jmgBIl1vKGG#AbWq>Nf1*f$Ks?QlTQ;KNP!$S)v2m!Va5ZXtNw z9pdDcf(Tm;RS9+QVW&Nj4OW5M=VK=uYa^SIg>gIHMV0KlC2MtzJ{TxlY*Y*btoDYo+=w+Z#{sydwJgwr zKl`A&PPn<_XBR)1HGY9iCOL~4@L|$SJ1n!??C}RVOjWbQT%#6EB)zlQsI*BfE(&eA zWkXW>Z|O+hpUd;Oj7g_sCZbsQ)SBUbGM?z-fOID-jW4>1Arosu7nUYnt1$0@WxoQ@ zVWzkYWqDb7?y>(CXCL*7YRB+{P&U?g&*CDnv}ChK&L3UF9cOiGDBG4cq;q%_I1OzT z2znWc=xuoUSZa5 zgQWGOs5WkR80e2Rvx=54^|Rh^zA-w=Jl&akPvy}=EImx0kH~frv)#h96>iWZj9H+U zr-&y8p0Kz}lj>=SnvoBp$rfG`Z?`26B%GNGi*+}HPuzSR?ygo)x)OtO1`m7Ksc?=Q z3O zTzXOQn8!RCn>KBaH$~`&dgvo^!OH%scunD}|h*_nD;?STGj~2I;AdW1^KauWq$5z5F`j3zWhHu1;F>ZH z#_y`{yr0&*AJqJ?V&>;3)~eRq-puCwv;#G!Z2+-%j^&%3{dbo|l!1J$=3!Pc6isZfnV!mdx~ zMA>=vyLHJ93|W$z6oim~_1#c-cdnmf!Ie?djVR0)v5c!VI5*dp0;OyI=}os|zE<=! z$Q?(U9lY+bE(webHnAw}7SX9~x8(**aJ^W~azVn_3(S*%hW&xMdzrGB!58*^8#k@o ziK97>@yaqzJM~l)WeM+7kEE>iPEV|9Oj@05-+7xGZNAaVeC?xY`I`B85WpR`@5MXb z{w}=p9si8uPdFCmop&yu_{58gGtM{z>yKFPSC&UrH6Fu}XB~6YF=wq?yZ(hY-g482 zZ@uld_uR7g_J1aMEr3xFvjDiAJ9paBci9%Y93SHxzwE-hoKkGs`cq8tiUP9TnLN8E z31*qkfO+h~Y+__@rp)d9d&(Meqsb^fC1tI#NkCdc(>vBw+HH3NEu4FaK|IH+IzB28 z_VEcODops^dY@fd5t{CBr@;xQed1HREI6?mnZS?=PyJ<^O=%3lcpkC0>u2FQO&8ov zn&o6S3Ja~YSP)iO<-L(7a>o6KqQ(pC)!W|1(fEJXOCNj}fW{+hL1ypb5D~Gi-%Fgj zzR8xXlO7yB<%&~=(2xlfgzo8CG}%>8IBK0T^_y_aJv#G`d)kP2pqp&!gS-I+Nb}#t z*%Hr<{59B9l`z@BC&`k{GSWF^Qb%gLki>XN**`6Fh_hM&N;Z6$W^uA!2`gFDq`W-2;( z^9-AnWN0GQ*ULkeUS3Cy1$?1?*w4Q5jQzkqi9vF_>J5vPR`-6@*SkX4OaLkI+r#D^AM@~!1 zu3f*OUps_*J3tTM`@p~%yJowY`(u`)pY_e?2?@P1KWu2`;a}2K*yqt6dg4;or7+jI zCbvuwGrrgx;*Asj0GV%UT(3JEV8UWanAysB#F_Gmwyu2F;EL_PJbw9AuWRQ3`C{EK z8uf^ydLK$xF-q&nXCC9St|y-LS{K9)$?P1)`;{0>H>%B#0eI|`FW=KB{jt5v zuQY3&$(%LQq`E7o5RdZ*?4_s?`m=$v>=AiWpN&v^w0|(9xW}O1M0dmn4ugDFf%iQ z+B`n{x$E$m&wLiI`}^19k&ikPmtJy_JNMkjuUfxBM_i#iOOE|j51j%l5Y7m!%+RfSRtO?K^ zY670CJSWGuZY0dlIhZxYK&eT(HDf_)V-SDwT>+pmgqTV9LhF}xw$JLjIzfZMd@cks z*SanB-m<9~SWWo{``<5f*O=ZiC7K3D&osos(axW*dHI4yV8xFO<~URCu=&B$i&tI# zhxIM{e;2C1ZEjN;a#K?&5>=+Dpgb zu(?#wJnjVQ8=IH|rfdh~AKIiVW4DI1zGRe7K9;olBhoR}I_(h$xk}szV7_fg5)zC_ zOhiu1yY+_Y!jd>K&fGN{ue843K#hLn8Q;s~YxQKZELoeGJw*J2Mb84J31CwPX%gnl zre%L8^|6F=rH%av^haUF{ayLW?I%^Q82+xz-S4-4WdN`O-0tf7CX_rL=Brpdn^b*( zX6BPz_g;f2bG??+mM=3bx$Ici-vnUTv)~T0yTL<`w$Xz_YVVO|Ah4?_=~Sw=5&^Wv zJ#Ny2I7@M~CZnL~I-zSshzFTNPe%@GwUaJ9f(4I8Xp@X2w>l{gv-qG?d9t`DG`5RJ z_6)8qkueNugJeubLdim!)5?o1E{59zwP+pA#36iI(b3e>zW6LtZjUC@&HJjW5pLUe z2gnk&_gFJOk8)-PwQsz)^2}sR-#Mi*mSBXgDVF?X3+veZj?Oi;TXo&kd#stC$A%3@ zpss6t?3z#DzyJ3?!hihL-@q%EUX6FZ`(KKE`}P$_9J!uFSytoGcr;*fqgU%?E`{d|J2w+fvH52-H3m(Ej%*@k^ue;EijDDysWR2;dPwvS zr|0KHgTb4J!8fm6`PExPNbj?hxzv<7Spb*^TSW``C1Sm=g{|Vl>@sh@%WiC#uraY@ zN1VyX62GTLBntwsO}V_fbFMQtwyf=B_a!%Nph2tKm7nj{U)j>D%fxIR;e`=qsH_i0{oH~uEqUOJ}6sh>@^ z7c#LP>gN3BVe}}S8SL}G?i;ZPr!w2Au~_tlK{h@%Wl+qr+boNN`W8Xm*cqy;64&#P zd3#@3GcnO+W54w!pwOVqcRw@`HE4D7yTJf&&HNDVSAz#CC#R*cRhD9(#DVD)7P=jV zq5+F@c6$Fb=IXNUla+T3ns0yV*mop~aUtR-QNqmIxUMnl{!4kqWxs(P!y1d*H2&Y8 zD`0Wa0lV*H=|11O@>v?aO<0ts8fcOs*iXjh0bB}bM8V?YQM3`0c=Yz5^N#WTwPI-I zF}3ob=N+5G@5;dv;z8!n^}urbO1dLTZ*|WDBiVJeBygBgo;O*_lMY(>8rP;6sP|ha z_h+mwlhq|RPpL;rgtl;;jgh=%);BioF=1icB+?k043XyVG$W0L>z26A6>zTc+;S}7 z=@T2JZgR~^<9eMHeQH1%4dX3uV%Q$EBWOiitL2-5#lktPU%w8{IgD3E_`nA~gb#l3 zYMgMw@i_my^Kj{<7r8B49_2P1d4yjXjVb~*;|32q>Dc3b>F6Vm{MnEE{oh`5_x}BF z{?^SmyyL>N&iORSX#PIL&|QmbZ3#;g)6v{$-kJl=jZ-W+H>}<2w5c8a`tr|YH@Q3) zCl6wH58Y4fz^7lLlW^*B65RBfYXel8<*lWf_sXuj zSB%G+mY3lg>{`6rUW z9n#o~KFxEmFz(PMh8^q8b$u`WiTj(6Wp>Wo!EK_9XNf_w*N}+(=|-l7?A)}~TQbZe zEso3*<0lV3E9Fjxe555y?9RD~Em1>nk=^-r)6#9&JRs?8!X{fBlT$*CY-*qb%==L_ z=Aig3xAW55{LbBfip6aumUhej?Y?l;RSvsR*%fvrXbYGU;3P%R zehgs*ODvD~`<3PQH^Q#E5Ke~X?a1%FMa8!Bb;#FemNZES8-4|?*SjZn>P4=)};MOg|9U6aW zwgFlQx*Q#xBVFML627icRTW5L-P(0nyLKIJy7@M|@l9{R%U-sG|Ma5Y#Gn1?U$`%R z;kvRY%5vTMb>ph4Mt)ou12=Q=(MN3htw)}6`tIv*x#1ljyY`bW{JXcl;Y^aYgFEUa zE_s9TN?chi3>V@#zNw%mP`4RL*1f$p!zp&CGz+!weWUg4d*GxV)O8EQfqg)1(zjwF zGlvdzuIVh~dWNz#rHFvZE^F3pt1qeskt_LrEIk=pv4g8P18dy2NKdc%_G9tkc3Rrh zuUT4npLKLfubQ^AnZY?j&pRaqIA+aS!VGz)W0L(AMY8seF;g$>-1in$SL@phkGG6tGC7Pd0Jwet2x0Kg6{ z6ekynE*OS)*=+4RGT{{((}<=_dcEC*Y=CsFIJ5o!9$@8QaPS^TbC1c)*g1xE(?m5+ zYCl;ojc%>nQ;F_j;?FiatatBrPEKOE{zl7$EoaQ??}=JH(Y8MyFn>c1X0*}RbH@Ew zEM0gBmUdUMQ?0mXwl~7Muf}hJxv#*v0jxDS$j_)Nj$CYCV_5%i4`zOcD%JMo0u)i@>N!sK*{W}UKv^8k*=X&}LvbW-W4henHIoN4Lh*WcpFS zo0-+kUPf6EGwarX0KW94FXOYH`#g3H--t(?@i3l$-ucA^7d!!{o_Z<5~~{;16_z3!V|+5L^1zV+5me{s)ll3!^<16*lWmS3>-#q$6C0G)Gn z0SBY+j-Qrz4Y;YsRA#*>5`A-im`vAobR<{Mws1N9ZILYn})+(2A%b^t$Qou45rqVCraz88GD&6nIP1U4--SR ze#UOUcVJqY8f^(I8DH6-*R8i9W|^jkDN{vI5rg=&4IFipG$RCal z9yQ2yMKSyP^$WK>1=xM(efrD~cS45dj^Vf6E4Tfr4cu#NRE@2m<1~X)9$-y!pcv5r z^Dxi>wXdsj{d@R>>sGL9k4inKD_9=IH1W6ZX`2o_y=p_``HgJRlI#KY+e(ly&|$ z?Cs>m%nWkMeqOqB$7I{Nb9cyU`4Z8^F?7wFzTTY(ESWfine~FXJO!S8FNXLW;ivyO z#P<^ZLSGxJ(HP@#jiM;AW_~Ta_xQxeKaD^9!#~0={lW|J;upUZ|1f;L`_6Z+FXraw zSd_(hWo7@k0FU0hZsU)fb@C(s^0}8jFuwJ= zTbE~8*c=BKn2VoB_@7y?J4~`|*3Dzq`Di4b8cFg}@^M+VsTSuvPLoR$J*M~57<7bl zjXl$TPUwey;UtYhNOT?vsSahy_to2OCDW51i7!t~73(@E_S&kaj$2JB3MY1CyYIVihL(iE z)*m@6Iq+_AWKEXefR&A{s@G!%e^tJ6`-?z$EG|;J@nTiLC6C2LS1n%k0o3?k#Y{N> z{TSZ23AL6h6aB^*X=a1vlT=gUj@k{%8CKO7YpZ_;i;E5mLuEPiDRTdX76hm9;Ljds zl`IbE4PZVtGM+Ri55HV>KaX#&bZr`VH&|!mzqO^;oiAxxZWp5-OUl8?)G{Y(L`|k& z^Ty4EADB5B9h!o02Wv{V*@PGc3`JuSHpMDB+h4Mf`H^J0Tkm-C*rb2rCm%Vc_??EN zG{*uX8)K$8*KNWtVF?~P7BueP7j_Ii7U8PfR{j{?|0@T@EWC~5xs!F7Nc?auZ&cWi zzb%MY;Fz=JpswwYv3S)xv29x^QT;$zl-J|+;qyidF~Ul#Cc?MlyDWRNUSREgA}%`# zSVBC=9J(G@*zbWO4zN3vcwjJeR_%J?bX8TMOwVGFlDAp51J5qW^H^fQ(=WJ7h59U5 zO3H#_VtLk$FwgeM``GomtRXKQvNT(Vd2kc#<1L**(@u<0x+7dHU7Pmm$-)4=_nrDn zh4QPcwO#JeZ%uFf9@x4y6u*CyzkQ={ph}xyVvZW ztHGIqg%x01R+i_twK9(7+svHQL3^S}`x&!MT%pT8t*+#NnFo0m=p^SRvkV-Iot=VU z*d1G>k#LXY~3=%*~VYzWdxgV#c5WFlQ{e`gUI1PBVo!HtpM)mHW{W?~>FvEPX4M zbOjCPb+`^=SHs?U-=-d#rEd6>Cxs;oXzOQCPRY1S8bl^5qM&KDWsvB~m6_VL^b30} zd4IvP0lmdpa=luTd75071q|NTwdcV7m%*#HZ5qF9>7@YBlq@W1F)tommX-{QaO0b9 zy`tEB^qrb9;SN{|CJMeb-~*xB^s}P9zxV z8yiQNFu#Q+6V|1`vKK7a#Kbu<_#xII`gR-)d3Db-QoVV$O7~$*1MK3vIV~ViSI0*%*#a>7Z}6D=`srF$#y-*`Hb`W*K97 zoM!M2D==3UzktOH{x6n>UsV>?eLUT;Nc`T?t-Rv$7t-M!p0>`{HYQNQaiX1|A&uz{ zkdE(RFdJiTFlXcH-B#WGn^=Uy(r$@Y_x5!J`-N;nooRF0&KEVQN{MSZ%VRW5G^Eh9 zAcO?0gJrplX#}*~thpb!PIRK_%@k=Tuym!zek9S}ZtR&KCDG8tGbhb-Lc9CS%o)k9 zV7kDx)T#kJd8PMyh0hY=W0_xK@b217FT!;O((S+<7TR6=Xo7SMK~9sw&P3emj*ILS zU|J`nBQr4{srgp zl1nZuw{F>jBQ_psD=RDGy7HKJvzs4#+SVVaPV+x-)~S!a_3?LXoqyY1AF^wfzvynq zUJRVWoSQ-69co`EJD6b~*1GOL$+0t2t~gm*nAHk|pq{<(8dE*}yHuaag179PH0wl{ ztbCM{Wp8=iTe~an+OF|=(W4{G+iGqTY%Z$@Vqah-rgtesZ;RXVVBaR>*}TJ{W0aiD zvnOtHqUmHN`C88~n%z0y@62x5xs-dJWPS~am}$u}h%w+|EQ85T58pVSAQRVuxK9T& z^W%EV(!Hd(;YqEZy&t$qJJ}9H}`rsH#paptC2PN zE#27Q$bLoPL{p9?d9p&`W*u;|_w~Q8Uw-AkU;zb&%5s>F2fRyql~|f~JSnd++ zFkEneu7i7JX~|<@p{QOy{9v)P?cckZ@_$=huYs>u;916vsV2r{dTqp0t25|?`PSoN zb})z9zr)l0B$htXlyxpiP&^RMFo1>T(`}sO(*3&JfTe5rkzU6(b;=2it@PnP;H=WF zQDW6!?}zM>&lQGV1D1($yI$sjS!zBW%J(_}#gYIlRpy(&wFc1avNkLW#%!|t+X3pj zvT7f+KF!7?otW|XtoNMVafY2hv+&r7I*Hb!HcC@AY4%B%Sm3uolcOWk*K6Ajx+&pT z>Ng!WV5gV`%~NGVm&O_+fvJD4DSuTHWWpH^&2)|{rdH|<<{HrrZ>G6uXyJFq8UE&PcJcb_Zz$KVUstSOzs~k8@2~dl-8Wu8w{GJPo%GbX-+lCdwm&@cCHS|S zo{N+11l+ZM8MiLqg_UXyR0KU+V@*HNa+}5|UiF1KCLf5Up9;I(UEls>L2GKP-3yH^ zD>rrd2abNpS#mC8%0z4Iu7mb_wZ2g6nqpd7y?Q5JNRbUS4@NM)o>7&X5=Yz5=WUyjktdyf=6=c;Eh5JEfa9v&sO^xxp;VK53)+ z2kVzy`DSd}R${0woD*EtjxpY4rpCQN)2v_DoyzV>(-{EpotbFXM5YN!iss2f_u2c( za&KPbJuI1GxYWy(P7^J>Wo1U;I9+SY;2K4&Ijid0LbDL_NszXjKNQ8>!a3nKglIT_@v|H}mGr+Ze=vWii7s{@5??{ZYK+ zJ-1*HPT6qxB3wJ0=`yW4YX^@>y|^Fg@qhtiqC$6rM05A+_o|fe0r=YV5r~7~(9OVF zQ#OVRp`o)`k`>b#VP*L%hJ)*?b%32M_FDr^+%M0#VH9{cb)lSgKFdnPl3>KrjP}0s z5;@t?1Wjeo#8ahVrE3bL=txd+3b%1QP+eh-|MDU@cWbvu$E z;mzPk%*@VW&6+g;fUkV@Yxu%-U*YieZ^T(!AI;02crng7_i@EhM;`?<_`C1Eo4%@X zc5xaWcl^WglgB;-AHVan_?KHhijUm+DO^9k6>tUCyET|8iqMs?tDf(dN^OY_RJna0}JF$~0D-jQ} zh}HOEVQJ~x74T#3HQV1#;y*cGoZSQk90&O!xctRUhemZCa8Q=0YeVJ#uJ-kdu=u{~ zvAC_o((Xzh@V!Kh$$*sYi?tgHU3XZ^#xN!&@5m~AIN`C$;vmPEvT5*Ro!u$#X?K&o z^1Ue)=MyD&ae8mW(Kz^AMP4%B`?p`! z8FK5b_O+Xv*~AU*-x=_0z*P>anYxeT5-Kd9s9%2NyXLOA=&9p!?!~SgECASKb?ptP zy0Y5n@4;3RAsq*BZb0KV&D$0JW&iRg0I2Ne`!hQA9cjBOI1i$1YIN76ZzV;9OcG6f zZsj3_kbreiG`c{S^5f&6<8tJpV_2O1E(D0v=YI| zrfJqQ>FFj0lANg2Y)hh?b4@_krq8imOV@P`5}28p!Q6a-x~lM@t3QGdeCTQ%bJRvW z_UyCl!V50oS&!X{qmJ5$aW%%>ckjhqUEuQhCt%xY=iyt`4gA<0*JAfAAIGQfz799q z9VlIiHN`9jEV9ozJ2%_`gvS19gszN|@w~AtfVQlMcYhMTr%r_F$Ir=g*HXcw=W-lG z5a*1(b_QFW8Cci09xSL~D00ySsXr96g;Oe|T6+D3>?Uu_jNH3BHXoHomo+eHSJ8Z2 zeb0oDVPLIaO5-gWcg*|U4YMwxiFq(NrRS)1i@fk!mKuQ_U=)kpwbFCdI%A0TbF%C$ zkfp6uBg_?-7hLlvHTmOMSRgR;P#h;7bP=n`2=p(x>aXzP^WNgtul+HaTLA3rqTmK| znDNm`ylJ?Grd0d}R{m-m`#-N1ulgqdz`{a-rJ=;Dd%=6(_+wfZcp1Ai)Z9lVF+@-Q zZfQH#5LYEbAqi*xyGny&%C}&yu{^Wp~_+?pL(-9T|KF1)^F>d}5 zJwKjUn#gD=!E^#Mc5)Y!3|S)Yi5PQ)lkU)6a!4B|_2vQJY+N{GxmqCxYoCTz)ked! z?Kdy|Z7l7+2`cls?+j66*Fv$pWB4@y|8M!4C;tTtd!8A#IpPsCJF*65f)98Q9IdXF z;oWBlds~V6&sSdlzOP^r4m)A;;=7MylUd=WY|b*1hx1LvEOS4Nr!Za2j_F-O2nkpR z#lDJu0~vY+PRkUZSR_tg)+KXzSK`LZg*5N{*Lgs7B}Uy~XIZvT{)+vQt-d&hDZ_5= zy@dYQ=^g3Jf&qPuV|<9kIu9K0Eg7Jve-4~@4eLG&*O`rHCaJyFL7CZjKS1iz#}fN` zOYI!cH>z)fK!+}>bJ?fU1cy!Xiunq4RRc84&(8q>?!03!-tmt2;GOSyH;y}gGtN8j zTwHR=MR??+9*OlwtmkMv#>&2ZI2K3Z`5T{t=N=*F&mWYeHCn7Hp;jTe8>c4bWcBan%z#wmvB?7Y4@!*xbsFn%WJ)Kg}S7CH~R zo(rBKv$JBR(dJ@dqF<8})qagw)?M~#Hd)sfep(aJB!91Gp+-*Cz5B-p-dIAIz+5?Vb`>>zr_Rp1B>VWCFa&X(s^vP+MhwsjUJeV z`Td~XV1wdw_44SGSlaz%0O%Hp^7^?ixK_|Nwt{z^+8}osrBTOXCn&}3l|~#-M$%+N zD_k?hR|nhat9DJ1-4v~g$w6fv@|O8_^^MRc%FKO}9?d~H!_;%q+6^rxFBK*9vm|Ok+!DJtTA1w3wqabw_C2>5y&7wego@?Q0b7jAD-G z*T7q`u;AngcR$!MtYIK_F0y*b(nkS&6pP!A91Lbo1FEyF9-ob|ZAMWzGu-Z2dvMiFnxTyrTA1BN&oc zw{9J4U)y(XxEcTO4{yd>-uf<_cG}6j;DRUM!VAyGnP;AX`88{?JX*oZN`+HqPQ=#Z z9)bVogdfD`m%oZDZ~Z8C-}MPxSA7lpYz6b2!x|ej$LTE!K+d|PmTn5oyI?lQlq5AX znM~7&KkmHxd|^N&&)0s7-6|m|6R^zOJ1v##T5_A2`B3@*m?!CstI@ivh;<~u#fU6vqc2`;?-gmqiT`Rf} z=*&dTfZCXjA!*!|k1eeT=T#yZZKxqQhnQ~ zIH}fF`wyAnj&`%b2pW7_41gv}-n=>=`^AVICI)cx&hY zEba${0ZWF(MTf0>h#kY*$G|7h{2X{(+O<&N+M#J~y&nwY1xyyq)-TO>;S(XM*RUZG zvijXfC%@A6D)C;McS1~(GH#Dthl)Jug4|N7+kEM(Dm!|d!V z*RELuZ-%dZ?VI@0m%f5Gz42{0SS z&csh{ekMM(?+du{j(^36Zu4Tg{YkqMuqWTLiIp$yrJy$`V21YX5m2%W351__z*Z{Osca4s+9%`%pJ zJYh)J+KPmdI*X+qb+d(s-gRz$W4YLuA-^ODwk3o~mVyrocViCUTrOr^W%4x3l&lvi zdqNs}u6s6lT_nk3NK@szkCw?8mljQv^yYk3*PJPTAB&fK8aswp!xkO5r1A!_N*0X6 z8UV4lNbK1|Y`Kxxy4i5;jl>CiiEpp7=J%mtX~{$0J_jRVCB86|F$!I8qb;rx({Tq~ z%4yHniPoBVnjKsQR^^G(U+hC}0jvhDH|2R-b=@IX`#qz#l)eaWGRH$I6`w;0dhpG*^@t`o__Hj3)KCn}U#dIX0VBX5KpY zo~(;;8gE$Eyo4S!m+q@rw@2uT#~A=zJ5;90!Lqc}wVMj#(n5PnutnIq*>LR^!_tz3 ztM4W>^m*LU`Jb}u=80rzvFIp^EDICJQtR0Zqe#}eAcT;Bb&#w$!z@#~f?)1#X{z)@ z&urkpeaFx%WkV)f#pk!Y2(R;U$XAS^l&d$SB27upUxrCp-2tMY)FP?B0A}4=&9@a8lrs zi_Zi4As=Y6C8l{=rHeuY!6YfyOAQ;o`_^9V6CdaaQ-7$b)})q;SI7mp_7BTgnQt@y z;BYMrSe&L{G^O}E0qku5zO%hOu=AP<#EoO%MgX4z@K5;N3xBJ)d-exNdl_cRbFHpN z0L$1sJZz%$!@c&sSr#w$K3y|RF3+u-E6o!%;b5oWS1;UzrK@k%74!g(OUh>Z9uG|D z79l{>5W;u8m6-bOk+61yu`^C!7C}yly(KaVApz?k1}6f**rAmPx|^8@HDUbX0Rq@O zs&Q>lU_Fr}D=)JCJ@RB!_JcZ0)8l&@RGKNqAZGN11j`aU*gNcrZf7Cx^2nSCyRyO{ zBr!WPfSaF1Raf}XhdzuCeBgsP`skx^_SuiYC6``gk3IV=9I;^omq#Nk z-#x+xTZiYa*^Xzgeq1@ECMxaznWT&MGWIMvp|V}X#7m{udmz8H zEdk2*@M#4`^%!&IIc~$~SN$Shg*^*JoB1Ndp|P+L_n*ye3Ab^=<#V#pYO>+fk9{!N z;3norC0hB^Deyke4mQ<_G%H!0ki7CT#~7Q1IHOAzYi@R|FgvP|<-H%E7B$?wbdP^0 z7D4P;Xfg-?)9&S(cq10KT~*Njkc09kFs?^{m9$)w3R6~Q(O}7^+-{_qg~>1+pUXak^$8{=ad|mOsok@ zLF>z2>7be!Ftdxu{FGH^JYI{LVOeHH%mT~P_oL;}v?{!GchV;RmTO)~lb@J9)rnEo zi$(iBWj(zQq5a@QQc+llyP&~znRa<|pL8P1I~?Vy!MT7}n@3$YYw-EGIT(T4Z@&}o zc;~zEj(5F>C!cg8F1+A;Tzc6>c;q9`#QfYmmY4UV8dW&8I0=tB{t@_pPW&N!ZvU6@ z;X6N#58ZJsK0EpvmWvu|idnGodD*~3#uKCGl4aR&>;ikHChmQ2j^sSF#CU(&!Ywtl zO%KDja;F1FtNGe@!3@kdWwNRFTxML(-t`^cHj~8^v+O~OfxNdi>*?F|E8h3o@6}tW z0bp^<494?E!4;baGxLx3wLg!*d35e^R=V}@)_5+^8HplXXTe@Bc)n$J!^wASy;93e zsJ470nl#n2tive)n1jQ(@+kQ0c-h56ylVJ)Wnl?%m=McntQ#|uxjOUHZ2kT6W-i@r zd0CD_%s4i{91m`hSUWS17%i~~LzyM=QzvtEju?>oUYoqaqXxbvvvT(KaM`YZnu*6WmZ=D*yy4Fw8EX`OZ z317pxBQPj80RZ+aP^m*=Rnk8#El<77x_|dpX<3(Qv-BFs{B{iloo_}F?+(e4dt$s1 zLIT!11nwe%zMo=$s>u01)^|qLnu|k`yRy<(( z5Bu%fCbgVI*z(>il>K$4?)e_7!7yzv#4HzR`eh6Nk_2I&E!wL#;wxN%4eK`m0B*eD zW(@z~AMw_=zXOkY)R}nV6EDF97d!zcpL`O^vc$gS{qSSMW9J`@^VdHX|MvL1@vnF7 z!Qb8Zc6?ypCosoZ6mY1mGw6!DpX_-SWe089=M>HXrs@6@>x*=nwuy4s?z{DUH8MCh zaj>d0u!_W;*Lo$sljmO)a66CM7|~CZjl^p%Ttjg9!)@!ZdGmg}`^yebIo;odU3qB4s_^Pg21}<0*qtDuwP`Wy~!dP4+w(e=(__aezz7NF)-=7#v zvDo1$U=^`(*M#4?XRcZWW9xo(t1IK@yVHvh9U63EAb+&JE*1Pbij+kx!$CprLj z!9&v}jALmS%MI z1-wZ^q9|ge(wlMmrJlKMq|e3FNPn`GbsjV$mOe(&GkylmG&naugLUiHz?;YC_B4K3 z!`HnYXPvbbmt1@y&UwN)*t~f&s=C4*ckhL-4VSEWJT5qW3;y<|f5Pv7V;2aA87rF> zMMQ2U8w34Ts?1Ettji4QX9S|l2=4lH(2e*1PWRnyJS@$A*Lf>E;i%p3RKAhy8#+#&JJvhMHGHqkKMpMuUNY5 zdG+FZ|M4N*m9Q!8!6GsP-eJ3_YXZLqx1?YZE|wl`-E0^F7!EzARC(WH5IY30B?htz zs#e{3xJckChdrAO!(rzQexTB>r7U0|cI;?^frI&uTLFWv@XZ`WS~Qa}rgK;}F12FN zgo|Z=stGBjF|pkr-1%3H46~)-UZ=7oILJhvJfxcH$rVzZj{81*>q|=>3kwB?*Nm&h zZU3>H<>Nq}j3$BCB@P!%%jww{7E_x6>y>dPn?X%>Tr_)y!Hzj>uCO2g1TV8ad%BNt zs51F`{M>ddxMqwP0>jqZ<-UsNVgBmIrh}SKoi~S~S@^Jjou>z6>C!Ce%#EZIX}BbM zmM}B`wXKc~-bFZUU0^Fx`L4ib*pESpr(+h6zR=%{9hA*SS($e?<--SD5Fb4&V1=j@sW3 z?Yx+nEdFhJSHhI_}r>+o~u zzF~06h95WL#Wqts+QwDYJhXL$pYW~gJRA&pSuT^(K+6Zq31toO9LrXrpaac}1YV3^ zz3?5lcDRE3XY@-fv_bFv5djwtaK8m026Er+nYFOsvS4~C@wXX%7s1y9E?BkicM1Lg z2jj4?rNl`a3Y4zIaz)IQhC5bjeEymdcB7hVc%Ghrxfr&3g7*9J+230xJarCxPuPI@!4ZR^T5HY*08kMX>f&yoxD(iWCzh^R0a*h*^Wnus zhdq0EzrJ5@zR6OzWa{ShxxO1oyA_~uQbfwd6dsg|`%V~^0F=&xPuJ9emS(ZQ5LrcG z43zq%k=ARO!{*8|;Mniac`zK-_~Ua2`1##mb+6w37rrc)Y&;%;M)OG95y^)*XzP}xp)nae_*c7W@BQi0I+obpKaGdagXn9Vam93p!?s` zoVZ$qv>;Y21VLd~$M^8`N6g}a6X#KljzBrHzI5e0FplfZihhI|cVTaRJN!59!qPYQ zPkdmz7K;14ITXflYZiePJ8UzHlRH7G9b~37`s%7R4iNegILHy|ft?eY&%K-X7vV5- z!7-)Ho$P8mzViNLmOO=n;uv^tBDtnEKj2Yw1y9*t*Jt?^JiERP6~E4beZend5B%th zSo-KK@i~Jqd0$1NdyBmuh1R@sz_0PMyW(xdDm|GvKU)t$1M8Ca+MZ%H&%Ed&O z>yO8&XIC$kp3DTXD6i1+7^(e_P)j~7p6n9hx~_;c#11-X7m)NhyHco00jvLUhELf8 zljCn<8gHC-e&hotTY-bYzA~&YigM0~l0{KjU6&>8)1#&L@4@%FOV@z{SAclw`KMu~INe!sijk+5WqAyZ z$9UK#^R^zaIis7wh8fbE$NHHScgnUq@nm-k;BTtD{|+6#;m7`Kut!2}?ety9P_UuZy4O{SZ++X^wFz5l!M^)o)x=bX(#+c9mvQeu%tkCM>){!an)^ zmr=rILpmocUV6GCE?`+)VzoUMV}B|O9%&`#NtB?AHZ(B_q4g1F=>X@znI=Ro=54>B z{HD8N+oyb8@AfP8KZ76o95AfG`!qke@>NfIf~%_ld_aT0V{4DPE>OZy6kutr3t-^j zD`wrd7|qA(rg2hfbl#dWmCDyOJ>WbX43+r;1qxSvakTXQFW!6L{yi40wrnwQ$;Spp z1hBa6$U)&w^;Vxw;%op9CyLF0KiV7D61E03XINt}cw4h(jLi?*hciyQ%RP;^5d4le z+-&3Vx9IIFwd0p?XY~#6>it=xq{YQHJ~+I^tn!dPdY@zjC1&muQbkhepDOX0TRMWL zR5yF)9^%Z#En`onq9uY$^0&lqDeD%NiT~~6nC?bewVo8QL&`k#M>&wTPTc-hNd zf^vQqcP!tHpFH+ic=w$j!L7Iz^O#SBtL}>kBANMKFHEvm5}UTlW-Ie2_4>>M*?!ld z;HuVqh;?3J>1zVx)qCAn-gT*dHqmVm$QwFz<*l6B>9ac*(uM@_IakBvU@@3$1fpAO z;Ma=RY5pvk6IkVpQzJ`(~Nv4XFGt;*ZZA zV#lz$k9%l_mt1(FTetQph4~peuJN!L)aJ4}>^Q6%;o44&G&-ZfH|tYp%>e9XOL}Kt z9R22d-f+Jr^&nh@z;NNDc|7WrCzM5TAy}MmR-eJLIF^R>CH$)LvZg7Vm*SEM=ZU3l;zuK>iKZ?Ef8SEJD2LLQA6!$wd{(%y9 z04ra;eVeCyG|U&yV=ZX2pbf*&G3}$Dy8S1BDY!|xO@+3H{GrH!wtJ}HHdo%Cj#L#&b za|6vCXRaL=W*v&k8H)NAJm%=@aMrf>*7fMEz#Ug($8dC?g{(L&Gu!=|R;nN7w$%SJ zc=m;^VEMh9xQ_lHWs}O8&DgZbyA$@4?EILx-mb3Gu6-dmvZ&c6afXB8`AlWwb7U5ECAYhSA@a{f)51?{zB?i?SM=yE*7w*@!nF?279DVVn zXSy{wrzp$w>$-XZ%$>>t^ENw}1+0XFYrOquZmqsCeiDGsVi9ga#Q*J!*AJ?}Ghw#D z9M{0O1|B8K_9U-;GtM+Sz3mX(p7;ZLR5X}36s>3*9G2*s`>`+wbDsY8%9j5Y|M`P= zecu=D!v&Xqc7w%>A7^Fxq#Aa)tE;n6@+1^x0W-kYT?kcsU&FVZX0o>AbATNIt530# z7rLT^s{l9fH~Lp?`&W?f@}s@)0sr+gnTT?a*>~m>pA92i7P7=p$Sm=bom^Bg`0Y;f zcQL{__x;<&iSvIe7%pCLS~-|`I?O%G$a4%hE@Z@_rqy*_u{LkC6|inL6xytdxT^9Y zGa$*MrJJF1XISCRaD(FcuJpI~S6%)-SoIHn@4mNS$MB9M2F<^NML4_#1&O74rg%Cl z+g}ia)^@od%4T>e%G`-3D-W`6J?;Rr0bCKcQGvq7DBWy5D*h{g7k%F?E5bFvVI9S! z7oEMinA>m;=G?^wdpw<;0pMtmM_K8bq04t&(am@3H8@874sBk6IfKywg8^7sJ#fXX zzASF!tG0g}#*e_)S64UO{8=n5?LD}SCka@qgPD0E`TjF)!cw1wcH-{qO3C8)z~<_c z;!!GXC3aPv344;|kydc!)k=x5)lap`cFzo!9Tro)4e1&^tvQ^$OW$*g$ze=3wYKc@ zu&kqD+L}3tda)!4$v_mu0w%4f*^?Gy#!MnNk?7lyGGrIA^DD|kFfuDooY2Oyoyg9Y zD--%sp$PqKrrtOfg4*!YK3`=Oycx=}#L-6|gZI7vYW(&8{X6{fulyozz2jD#JbNsj zvFRfG^{sD0Sr!bf$Mg#Don?;tgWSbCauUQrI!9n>zb#Mg5Z9@0eZO_GCZn{*Rw=m)f`* z0W34;;+P%U8)lj>J8w4H_mAt3Rr#nFuBF22s&Zv{0dHLQEb#)E)2SZT0PeH~u>sH^Z>pS$HgbVh74+d@!&#pMq# zYrlg7_iXe2Y|yO%4Df{2b!}$ez#8;*a{>9-*x6j#Yw!hh%y$&xOel821zF(zw?RjIh|95W2y}&A8TUiuMDr*!K;^s-0k!h1V zJ5iu$e=myW9P-v=x)vTqiOG~7KI*wmP8}5J1n!!x+d*`t)e6@H3q+z;EbN2@2mT)Scm8R5=>KAcO*#xn_^=tdV`!_PYSSw}mj*-WBXMhzj?}2gcyI`V8HEi-$X3MFf*b+R( z#X2KjYrtu76RAi}o5D@0I%5}a6irP&CI-?_1>#|jKxN+q`E~$zeV>H&W{wPt_FupF zgmUBH*#sBN+Y?Z5gENDTYoNy1Dpb+0qgxP#l&3Diz`|a&Z#E3Z&J!Nq8$lk!viLy| zKj?}zx8REH@Ac;YFKWIGJBG_q)R|VUMvS}Q05F$tmNz=OF$=SqdSHdl=}bxu^G)Wq z0n+#9n%}YDu(VVIz~Ggap9_bdHQ2LI;so;st154X71jo_(Xe632%W~VX=~M1(K4oq z6Ev8wPo-hntpG)EiT0VJJ3lI&mrzDbWYw%IVvTGx3$J{qn#hK^f7 z{3NR4uHuAaufwa3xsvAJRK5FsAB>yEhoO*lD0*NuB~7Ed2p)~x3EPs+YfR<{%d-zA zys+}jC|b`WCvC-Lq0E|@Npe4}%3=%pzHrrf8Zim(!s{`0_hw&^$#cB=y%h73zi#2_ zqxA%%U}clb|K8+j$$ni@#@9x$-B53t!yKnXLuoK;Qx_8H{)s=pW^aght$iP<#xrT& zj;zy&<%(oqOiV-$^fJC|ns<`Vm+`P}+;#1X`r2dD#*Mi0s`p`G;YYCPs3TxCaN&l> z;&r#Z4c_X`;J&Y$w9oGH&F-Y+FF7L;Pc}Bjy6t=EXC_%9Q}qNK>wohg+#%qB6+Vli zU6l1Uo>TELW-9j|J8_mx80;A~ece{s+csc#g~e^<%F^zyV)3Hq7c;YOI0kN+=9kzL3~KBc)_Bz?ouN&pOO2er z7rW~_m#kSJ0yD3=>{;IUt7djNOTb1RHnx?f@hBU~!LekLKG1brPIHJYc0-}%{ou_3 zG}NeSZw61&!Q7>E<)y{gy~w}nNq=kO`j4?=_)QSND|#6x-W;6e?gAkg@on~B-%HBU zMmQEK4KZn=Zn?;Q@T(5yjcu$Kr1T@HWJ4{Rbu)*kZqtzjoHN`$`2Nlf!v$i`aK#?` z#9OSVUB%7JSU=Te_Q&qd>BgOdO>P(_wghou?`}EBgKeiDzica2ZN6T{a$PcQ8=PeC z+ceLiX1?l7Cd?{ZuI9|~EC3-Z?Z5;Pb_s@8UiSEc_yxc{%L*Qg8bDRoMy_xiLPm4l zre9pyI}inmYI37uYx54~2eYiI@rqf!jG6}2Y(?76Zsgwj?WJp$HuP*Nz?u=6M2W)f zMdiLf=Uh5R{@%7D%e(nPEBVt#oMN>BbzP&zN*gvf6mSGx2)xn;0kp~eLmyQZ-L3hY9@V2@oT688wC{+;YK&N6s! z`5t%JNDmiRf0Mg;h31)0*!#Vi8+I)eQ8awb_6v&2e%WZxLct9-uFY2bO8ZGWrt5g7 z%hhDVDvRzNvrYd@!zSU4p#;Rn)d&Vy!AFC|e{o06{w!X#?Z5d?ZvImYhc&isE3tcb zg)LhQSHYo10XhfsMRWFs2+iVavfXU)Y1<6zvN70O^CeBg9i20P#rNg>4+0{GHLzq@ zy!ZrH4xUfApCIjg6h>4YfM2n(g}2rSR%XrnXI{0CEai+z&=qFGFw%v!E*)!lGx(;X z34u8{JRW87cvSqQ;%QI%h+#*o|9~CC+v0V*>+le=4($@c2{EEQSzHsAMFYz) zl_!Ewny%iJ9i}A~-EHA^CeEJsVDZ=Ms>duFA{kB-0gUK3NSiFdo8oLb;YV(hW=>&j zb^v`>W|`SQ9H`k?yxqEu-rjpihRIxPLDKcH_xG$Ek<0|9&I*e8d*Bp#A&z@fOrsL< zuuEEIZ?D%TCMWA_YT4lLCxnmglt^5u+TH!47+~+-JMpD2e;Fs8bTL-$TEtsyR0UN&!z`Uzn!SCtlwb1UaF9F4?w?CQQUg!-^H-~{$ z^$07iLlLbEWfftf;e;5^q+e~9loif~e&^%=pS?ei)9kA2#PRq0ocmPumOTUryX+lg z3y`go>Y&azj{5aCT8)n5xFSX_otM@Ja2v}LFa1MmEW=$L+j%JXJk^DgiR-bv3oBkIE zo4`@eHnKG7Y=l#SR49@Ni9vENM?T+EALBj$mOF_xM7+f1))3t~H@>aIB1Xel#eCvZ zfQNLdLvX=;;;8s0yHoqRjid)u5v&HRNP=(;{lI!(2qkMyor$NT^b)O{y928?A0C@K zr8L2PYXSfD+%x@YJHHp=x54owP3wT5O^}WbgkqIdM47EX+C+FHQo5#FUfwMTsM_w| z=Q=48EUiJLTrp9mif62u2yg<>qdxrT)@IGw-S|^pcJ5^IjFXpjSU-S?I|s7>#W*EM zI_K10l~MZ?Q&5X3@hVRSACz)Q;^!L>07B$ev)0{tKs?X+i@(LKYJa_Q(|VE>_BBgt za2n;DB_)iMdN8$n?irK&=t_AkmVEQ4pW1b7L4p-H>_;=uxaQ&~6V>;*5WfYL+ZpS6 zhMBfVc>+~xR$RNuMk%`wt=m}7WJHRiX0|u5$No}SX_C%Vt!LH@(`kfr&*#Md&+4Uj zy@sE^=euWDuHTHy&Yj?f8``ORL;I#cxzKSOny0qC$dTQRNdOL8c2a+pg#>CFsS_np zijOvc)tt>8fVi^sOz-in^!yqS&r*m`h+B<(AC6Mh)fkHD*(Du^Ei8-}i8OUI4F^Yd zeoi5FcexGczg43`Q zFMSk{AId5wa+n{pY=NE34~eoFW&L@_@pqA`fv)2*o9Up3)cgX>nsasuiOSB{B2Ce} zC&!YfRX|S}uwyEiGT}|bhIiEBH2d)-m{J^W(r^N$AuySPSo169vGzP~+L$>)D60|j zgh^Xi+v~@&pe^*N-OqI-VvM-wo_hcS(|U@X3p;Rp`qf#+CEF(l2cPJy+ozg;;kJFb(qIN19Iy_Kja4^pkRct<^+&x}$1r;~7=JYii&c zobwP^D-EN`<=alf5NZ7<*a2642&!6Kh7Ir_!!?f}793U2UNE;X5df|QtX}d(-tlU9 zcV3*;5g}}m@Qj$GN5>dGTk<^&?Up5I5suaskjI!+NWBYl-5EX^P{1LI?=MGA2kF;vcuq?9OYtO)?Vooolq zLIm`5H>rNqeRS`a)o+;n7p$(|g75g^#deBQjajf~AdqFOQ|9YE!3@{uTt)FywVQsj0ngTs_ET4_={;L$yzQlv^wj{fuCE(ot7GUgSkwX(K>^Nx9{jdv z0l1}=2j+e}t;*b8we-~_uVBU9+M3o1+5+JrPOR!d7HnJ z?Gsqpy#OG(sBQGFEbEZxavHmrxot(Ry%?mXGamaLpl1uhU48M_lkN(5J{!|eYZkWv zRdn>pBgz7u?Wm&%p85}#p6N1qTgC=vaGhADv3t16E5;hrI>bq}m)`%GyZYkS#w*_S zGgw)vaQ{aqaDIXqA=k!wNo<0uSg^iq-;^GLEKj~U@7m3vc^;T*5db*nEIq1c^Xo5u zmS5Cmq`pCwds3VR0h+caai!!1+mjZ?Y7dycJsmZU1F}Yiuck@Nz=5NnBo zjSkgc$qW zIlEbORP#Z1b;R?VD0;nQ0^ifb*(Wzz({>}3Bds?Vhqr?xd^fXkWPsHmHL;cBXA?^e zBzwIjib>{U*19H&t_b7H)x8&&K6|(NVyHm?Ap`|Q)gd6F0L{igtSpt>`fZu}MwR;h z(tUgMXHz9mo7=p4I3CvsmdjRK3nH|n7zr9srcHCJzNt?bw*%;@u9<4?tJEfu3b^(7 z-U}0m^}dg}5Wj|Wx5K++ft$K?=Q zN+HhR=-^m2ehQ@ZO_X6*Q2+x;Fk@KtXHmar_Gc2IIb(^8twm)$Uc3Z!~X zKT$mjZ+~T}!YU$GmUj7Tm-bhcyAj^KMv@T1v=!obxc*FJ=lv*C1EdK`D;w`8xP5w_ z+CNiXOJq+mvudL~j#W_%+Nx&&zp&aqS$9`m{4MzLKbp2za!2$2*q^R;Pi-5c4hWPz zzf$f_d)CLcEEtp>JOai2L0;R+0#N;@vvS5%q6k*LksZDr;O5(f!RFsniSWtCXpbk< zFp3ax@@%INpV|<@nLGiKA18^iz0+LRp-8|E!X$|h9RY`1ad-R_RDeVe3vlk(NU6r8 zQkcScZQY#dj&zPaUf+h!I7WJ&>Q(J-zH6!PW0sdaRuS<%7oOx-mwv`o?pG1R?zlPK zYLULG!_aIPQ1hI1dp-&Cob)x<{F+F$_kB;^R?%u=raKxMO?Gs~R!Uf7I``7vRMu~j zA`VZEeKRT$*DW_8B-ifQ?XKPX4mx*TRJc%~t`%b~!lzOi%55)X?N9)WNbd#*jYg*S zt#Nv)qnna|^p2>UCtZbznIspz|F&v}TX$Dq{2HttX!jGr}w31jN( zQZh{25h!hNI_BrXzO{gr^PcJ6xc9f{-OnqAXKQP=B{~O(2^*WGf`Y(QuciH+`Mb70 zeuG(g)+@3P6Ej(7vKA+F4$e(53o`|s#LEA)ziRKBaNvM&x0i@*tuvz|Kvq6m%h|Sc zg!Ry!awNfaA5fZm>d~nyu50&>?|9B)b=4)`S8a3e#e#obRD6=Avn@CUI9I{Z!+X;C zW>Df?Q#X3oe^&Or2u7oBOy6w&4$J$}l%vhQ;F^DDSKmYu(RqRuqN1j=84#bWZPgnl zXPo|?YX9EL!4|w)SvtYhcAsqk7N(IvY;jv985`~X!onk}Ka5f%TcU>epD+Xw)%CFIfi$N7Mw_qW(%IgFfy(|#4SHcrt;AzBzFh(om10R! zxnk;uw1FC_WA~43=5wG8iM1DE#NxJXc*ZlHuC2{2czJw!cDL@S?*Y`XvvMr1tzCfB zznkeqz6$7&%!}uY!EMdglddeFQzD+G$UmMLScq{DdcYpffb~AJr*Ee#HzbU{ekmof zkF7`Vu>!GXSKj!cD14i9E;cDV$rz3U{U$_EcCWbIWh1A(D z4VnE5*)TVK-StJTKnKA?s<7`D zF?L1}g}%YJr*u;gwu6ceq-!6Hh-1sV>>9+YF8O@F&HWK4Zbcz%*R0-x5IPHE>+|_t>3W-@AdeWrw5Xoq69-2z^)Bv}=y0_NKLUXu?FL*sbeEWZnbzLggsMhG~2D!DiX5 zpu*Z(z<0m$N!2L}zr#iUO`6W8QmjEvKmy)Tu5r4OBU@%j^WQMuwMc%ey)V$_ln(-$ zW&>KeT;gE?66+kRa<1D0YwhsFtlSs{O^ZNb2RK43(?n_bEHKS^{4npXp zj}dvCi_)|1dROYc*P^me&#tb{*;wu0bAA62+jRB5HtT+gJUpnaFjdsE==`tvtC!x4 zm8A;AE<4H1nW)SHnX|y3ftg?Twlq|UG^m=6>dYSnui&t{USoC77x;zk*VE}Mnm``Z z^qh2ib)>TXqVu{9b@YIuw(1!c-Rt~I z?)rJ0zH^5oBAv!ZhtV?){C$*S_jmX~_LK0PA?cuL3P7q@u+ig8t0dL81yU%S<7&LdZz{%fMTM^% zXf(#TDSU~!#XjwntK50B>;MU~b(OQRMpT2Nn zm?5@@RCvP;zoVdEC@G-f{B90Ghy! zBc493VMVL(mubN5{j}uq=6A-dz)A^*0uiEE`Om|XC(md%D+eq&tgVH~7vaBSf#0md z)2)V4<$OEgYh+r($II;bC`Je!>Lb7%ML%)fvd8**Jz2T%rGD2*H*jHcd7Orzx~@U` z#)pgJ)l1p*#4b~)BZw6yNwMQZkv-xtt^v!Lo-klUH`FnZ1e27(fvgll9j8I?_z!+( zbz^n)#g|}py~gsg2aX{aWbErYnW;nn+}mlUC6^HS5JK%{+og>66*AyN`gjb3EZPaX zzvkk9SvmJ-fG*O;bgPNPQkABy?9XF6EVI*b!kE+>u+b>7a!yrw#1$uy$}0&ZQA0lo|bhmVE@d@OhZbLT|Fy_-6_q5>UjWw=iMyqhsP>v zyl&5F{m^z1zd|hx_-sleP0>NCdc+k0)C*w?f-YpuB>cYN>|kW zgP|b?_pBep*fXewfZE}XoICcOO8~Hn=&xD&VNU$dO7T=})iX85>TXzpC)B$4xKfF~ z6$5JI1B70^oTsPT@9cxP3d8^2)Ui^GwPv+Yx$mRXPb%WnUg)1c7bWq>o}pxE`(oLQ zeW*c?<+R1mxsUMrw~Rsmrln=?-ES$@&(!8@3xIE2DDz!2UDE(6fC%i)_2!?L3aR$b zK}9ykQ{9A6DAXu40Ey{ON0t9q@ zwgXt$1-X7=(lusHiZ(OraGGQ}DJP*cQ?|CCHK0~ciyc{ovJV)bNMn+n%;E0x#5K_r4*1ji-qQ{gnN2o&vtrxA5tX^_yTwMGEI=v`vY;83}(`L$-n4kkI1p^uih3jcI zxij}t`MpGrEoA>>UODaltL$3{I+^L9iovyW0KB&9Eg-%~j(^~;UHT5Jtp$uaRvtZq zl@Eh7l5SfZm@;fzjXT6$jGnllP){C2y>#v$J)O}j_I5%6(it47e*1Nt|b)`6f0{a`*% zSw^qx$%;nBAAw>N*6#tixxi7p>Sc|GuiRH7Mv6>(SW-qAc3tOf+OgrAAzK_A@G1wOjT3yO81IO+dmn%Y zPx~8t?azmQEpxRn5>y)Ajiuf)*%u?RQ0d?h1qsi=cRh7GZa~C25LV9JF*#}B7oB&P z#LelZ$Vqa?^ZglT4ZXYCvESp_4{DSR{2busF37Dq2DtWVYW`t)eliK|oH436nFCEU1#s$7Uwn(nuLwI%!KK z?k&$~?zI_0*=OH?_I>?fpU)0>bPgo6H4BdTRtH{!S%?&Tp7P#!CA!SwqbP<`BhaVQ zbeFs%E_D57?0lFWiHZBlQiZj(;I6skKQro=5aSL=Q#w|iOe`r`y0fkXJ2GMga>apR z3U%80#MC5H;cvG*(hR+ z8AJJDk!b|h9^v|5hQ}&|)zw&CwfFT{#BW0JS#fJNg+tYuM<;gnpo1EvUG+6`QIezq zt&BB?xu3QLFTpQs8Pxl3p=IT}R+-Qfb6NZx2<_>F4+CEtmCYXQs4?z08N zI&lH4(*ucCUXiu+^Blc<_7cklX!UTV$$D8d7E{lhUfFjDMb2MVloGj>O4=Zq0~7<8 zc8oCs=3RScRy{P5%YktY2SOueT~-b$pzupT25OaZt@QsbTTQ}Pz7Ua(%ai;uGZf6w zK=Ykhe@$d!N2@I^e8%jpNfhWFs~h^Dn`>lcYm-R@M~BVL4cv3@JvimmlW@fqufvNz z{~X+V|9v=Zax&g~=&$kad;bc%uszvV^o&3Qxpqp%j6Iw2{Sj?u>s!r@Wi~@!(hz5{!#cYl}cp{y6`{L?q#M-jM>U%yV#j9QA za_+eG8IX}RNLfu3q{Y=lJ?q$HO2iS-D(MyJjp6wVr`!E7@w?APwCkgOs2?5SEn36#18W=BbwPEY;8BugZ-(Yg>qD? zIBPN%G76Sz+1#1m-w!GJUAZN^D6_`+H;Z# zENz{dJLZGgEgPoBF=!TB@M?1PrQhfsenGRa4KYqZPRfLsZe1&duOOw&1GzCbM${SB ztXvp*PR-A;eu`S?g`HHxE^4J^_mz`T--&$~|O}>`SUEGFFxP&(vc~0Ntwj9dN zn-gQY6s6~deIpfJ<>)8weYox5KR2m#M_~fI|G_|LyGD%vG)?0gV>X0kOZ-5D6vDG` z^0S`^tN>WX!nTv&iUodYoYk9jZjz*d%+L4U<65?W?N@uo*lW2@j!gT(fuW5OcBp2% zfppJQZ>As9c{19U0GT3z=6jVDZv;S(h4{$#C4s;SLBelyKhDa1lQy?Dh3LfS`?S_v znUZZkY|bH?87yQq8cG9cMsUphC>uTuhV?@OA*slV?y_D8O0gEPtxY}5cExNuqg-U3 zo)_jx-|Xh|F=jPLH?0BC$X6f9P~+;QZ+FC76%;{+CP>3Uu1A+_A5(5+{pc;u9rQ4P z+|tlb9Aa6P7%(E>6aET>iG4RSRuL@Jda5-H! z$^d39c!gPnI$#pG7kb={zM8y_6Wp46UAB%r}E{FA8}k3JGJ+GUz|A z%ZOdQX`zMbWKShC!7u4-M`E}2x^V|jk4aUbswRjM9yoLeLcsH1@H||0`8VT7f9ywa z?kmp4J@?*=Q``>Rt^4uApZG-x5iS_?-GYCyoY>|5A>D*t1Bl)*NcwIhF%6ZPSqwbk zkQ#rQBapysb{y|o6r3Va`oNk3w2VeB%skVXYI7jert5V(<2)nWO-g(=v z#aXxs-c4eRLCBM-l5d=>)OP-iN$GogAf)lBFj04mffK*23T|)H#4HWZm;-G>q3eEW zH#O)`hd5}ADCQjZ0AMZE3Y3EqD5467Hs z(m7mZmmWPteMULWwYi| z4@XdRfOnX;A#f5Uwz-K^)^XCXIAV9cMGRb3}Yw7rH z+L~=OK4G4@RF>JEFEweB-9KtQ7yIYjlv7>!M7JNsUb)WfGYbeJBbtBI{5QmG+pfEQQo#M6`(#oeyB{`C5VoDHovyhNP${%|peH0z7HF5ZxR1em#M=#yUB?B}-k%GdRK5A9TSKj^ndSQLP|^_m5%+e7kkfuW9P_%1UbgV|^>_Y4u$M~{!KVV?_jK2sqXUMNC8OcJ0}9}-ydE+>O9Z_4ARS_eICOXe zQNr0zelpHK{}s6Cq6_itXFnT@i;H;R@F6^K@E}gxb}AmgL4Nl?{;)nY{Rqx-rywFG z8F$GTq%q5vhVr2dlu5Om`7FhnRZoe|ok%Qu$@Jf`Yh65#Tl!HIgHXBjtf$Z98gBR4 zfT{CGE7=U}q;w64l27+=Vv?``2nUuO*4DPP|E!;Ma2H3@9}I$jx^<8Yf0ZA=$j6@9 z*1*CuCwAcoojb((a5T7kx7x+Vba0zEJ#~{^_G>#!M~%IWj|FC$ZJ!Q7aNa*Vy!MNB zR@LlkNl!=QVNeT*4Nzlm?w~X^2C2P|l-a+Ucy(L&KG-TD_Ahy?uGiI@_x@LaKcc#x zHtYa(X_1^yAF)i);#WySz8pCOBij0v4cAVJn$aHOprG~X#JW;p1ui`^#kNtX3sSv{lo ziFSYN-fIsiG8;A_4UMzYyFZ_-LSFq3j*lq$yWQ`TcF_*8I2^1YM-H<-FlH+2{uLgz z!+}iz#tn_R^W`_sCRbhZvgq}z3OWlBQ!u*3)^?5=Lf9q3wR?k1cbsmCn)9FH zox4i2Szwz`CL4R!)t}74q3r*6z=mbC8x40uRchq2*u8tO}NJ| ztA92N5etzlFi)x<-Aj?zH6Th%rbFY7?%}^~**Eq9 zd;cY*|Jmd)fM9T)F03gxrL!E)f=n@gMP zm)Dz$_L7=zD-KMQ`)=KLZk_6C!shGU`&mA!v)N()egSI}e|X_%QTf+sR&T&*(lH9< z$Wo=$TfCHAH}V=)C4tl4IP~hCv07Y10E#1vokFgHGXE*Nwl3_ok@3pMX}!e-|2>my zE}hNxt-Zc6c3%&V(JLxODFxyyE3A$LVLDj*YD;4jr%NrQ$*=3j{ebUrW)0p~4s_0#NV3Bs(B^K?R#^tCTC2V$_ z?^?Z&aZ+0`G0v2Ooz@Ig$QyjMXC##YeaC9p_PD*))*=8#-TV90nSaH~yUP`|2kt)u zaH$_0bho!7W25JqJDgOVvhm$i^=~u{TX33m;Jyri(4>BviZ8K8VxIi!Y;|iI6Fvm9 z>6I3|dgIc!wcypPH3D(1f|X^Vnw<@8*PynH&xlV3NXizhL-%3PCOl-(O17Xx>{$Y( zd9r0KF`FvNNC$gnEom5Y-FfF(X>+#4#mSerlb`f+@jJrT0=G`*oY3>?Y+~&|AH7$| zg3|iz?jg;4u^t*xOyGQXzIH&2V){%DgkP8_9DS2gSF3w20`b$a4jy2jiw?2#^yzDX zZh)Xcpa?VUsZRpMXfvGTgBgud=1gPO?1<{Hja;?cN$T3ixt9iNoEXxsC7JR^YUG*y zw^&A(WkMNH%iJ!)p^X4Mngha7k(n>p9H5}zlnj90_kPAts*qB+zJ+O9Ek3kw#v^0>0M&Yuxn4LGVk5EbOo)JW250}{< zkdBBEjN$wRx9w6@?ksH1ra~+KX&0ml7=`oOqsEwQL7{r8dUGw)2 zO&_4g_M!_IS=-%46%0z6fdr;e?do0|iH=7&SX^21*tb@@t6u#LF2o;#!j?b}>%(Ab z*LHT%k_7Ak-3%5-M{TBeZN1Twf3opoK#~#vdCJrZe=4R3ig~90KG;U#1{Xli~#She1tiQP%wtWu3dMsZ1 zA*=$xcH}cLD>E_lNjAPl2?#14xqdsdMWl+*Sdm>H|%bd}u*|UnMaX&d(l3-U~I|<=L+}feGkv_<)F8(!6ct>0%~k z2K^}#{|qP!E4`#0@>%(_V^olf&je0-I)Hnd2==v*mh$d92>_v$z$mKq_sxLC#(QEc zCX^T$-X;*EiCm4C&Yl^`D>0oVpLoeN**@6-z>E>q*tSW3n5&; zRN>O~+P!h_*V5yE#95fNg|cf2W6W${#(;^)6gsDonBBBzIAd;>-i0!JTwTHMb4smk z4lON42kxY3C^mU4)3+#VKbSRmGy!k{xSW)W*x6S$evuj?BBl*9=CmQ zg@d=BT-7^X09Rcs#V;3eUW@{QX7sKy<}pQ@R|otdZ?3?R;V$!fwehA2T&xx#tcBT) z?`WUT_{fi<^kA9BTPTlem_eOc1chdebNRjR_t3T%Y@gkJ zkTDD=~=Uvk+iVxAcU(y<;^l;gVINUl-D%OP`PU-E0^3o zTU~#9#|-=&fc02*Xhvd_aZtS>yCq&$5P?anerx5$PX|y3Vv57VjmpdeGpQH)gs3$58;P&&wqI851=@c+v5LA=v!l@z zW+cKKa=6kpE_H#jY!h+L9&m!bZfPdqb*SIrQGGAK)8G^(k#5|~S7Xn=dQEQ+7#jby zB*#n_wg$1&dps`yAH*$p=4kjzhhBTKG61=hPXx()s%Xm^X3x2a8%syNCn0(BL0!KW zabVOp*eSABx6Dn*apO7s{1&{zRhN8;ulRP&S{pK|X>gqD_e4ENw>u_(E@GCI6_{g1 zQZKQWWoLAQ43n;ycEtfpf<0TTD{fP!n~D(fZj#$PeDtiaQBSdb;oIC*dp{PhTzkt= z6;6t5KF026PYM#qXI0O6=5NH+XB=iH zI3+y^p9&*MaEb~L9AnEKvDVwQ_T zmAi{S4v!aMD{hgfS|g;C9+fda2Qzqw`OiN(v_ykc-PA8@%&TAsHy0;JMgUYPg>678 zWn5OP*y)EeEQVua09ijc;K;Mk`UJ=JF}&r@hd$s~S#elh4epIgUqkYHv=wG>oFvJQ zRT-2Fv$0IJzOCq`^vXdfJ0?j_easOq+r_c4emJ7&)36YdH@{3Wx6E{kX0LH18_)2l zAjNIfTm27Q^og)(tVW-nQTOdu29pPM)(i7{5jm-^gZf)Y^Q%=GK zFMSpE?70Xpd-+Ro=9y<;TG!Y(yorPN9K_Cr9jGP~e0+90-hJ0!;a&H?AOCOTW7y;d z7Tf}MxJ5WEphiS(*+7PZnuB|#L_=B1#V|#2sO8Y0+qBEVHGq6)Dw;Oa;(!2w08s8Y zMsjM1RrCItCpTnBi7b<_U-&~a6OC)p)$|v*ZTpFdYOSs|k z5wNMK(#C<;d>APM%McvMhX;kuxHl2^LL6g~9~^xB%% z^lsH(y>xTfxAv#F4j%g+238>qgRNPt=U5nH6-_PEKtq|2ld)=)cYNp)LV;LYCiblb zf7K;dvGV6@W4a}v>iv{lPLMLl4d)uNjMOZ>#=aa|Wgw=I1DPn~KWd%fRiym(M2w!E zo1~fTM13A(q5N5v;>^K)V%98M#!2U=cXY2!fQ#mlXc_F3Iw?ga6BKeECJSiWH6u$k zl@N8Zy!#SXEJoxd!rmz7I_wI}EqYz&``sDIyM~mGty$ePx81lw!-c@&F2~H$(yaUZ zxs_EO2!W6T;})dv!0P)x1}s%rT|G9k5(|)O#5Mwbm{JN}8uOs#P%c$INf=l`FlR{2 zdB!pc_h^v$oW1#$Zt+f>*hya1Q;w7$n^~=F{hB)JO$iu`L9iUMDbsHHpb| zv;$EJ22^^*Vv=L_F{{}XbL63+}}w;J38?q*WEm`|5DY@AqrUviEuASv1UJWIe* zmzq0hU<&<>-2uj|o*W&#^B{q(=@gq=TiCX^4KLXJe4Ky&D{=mLufj8)_B4RR!9#~| z=)ObP<`;1C!cN?U`|+;Be~#;Ke?R{6@ZaK|`aW!n13+SInNmzDOE5|_njt7Prv5}}NhWQ*Vw z>)cGWn#Sc*8_jp`ZXD)6Xl^ea4ZX%TH2YX6PLs>t~BVCYfs9o(QjCFgM3P!Nl zo>rZoS1Q}Bfw{6i1A`N_TNyrL>K|Th*6vN!>fZ0sBF~Fkvn_fzv6qJ#RiAwmZ}-ii z>0ZyUkxjHd0+MOda7T8%flS^)3=)#YuCEc;hG;f+eYT$)b;&M#Zf)Oa<#dJt)%80? zxX(lPZp2!|>t1;V9luwhPDXIdHVX|0PO($V+96TSKIn*Z>3PnE3Ca!efquwsYrUxc zL)z=kv}X)hK?cz82^Ti}LK-}`v}dhs%f=z$%Fvl)YN6Hy^x*+m2bv4!9rnZVY}IiJ zqOt%|GP>_rd9hEY7TV{bvri>P))*5gr(%0=UTr1U$T(g9rxaR7X?vrb8LE&+0J7BG zggBfTCAcqw^xZ;``Ah&wr#eizSUtGBd%G!PiWMFO*W9lGIsB(9C47 zr4;rt+8S(3nNI@7rop+SQ9#ZSPWzHGhNoSl%RJ&fA20xgv9@FlBReEQR)5~cu(rpJ zWN<-_gV0$rWM9Mygd^ddLx>Rv4;_MY#8aR46r6Ydt8n23=i>!0cpi3a--bgQo7mde zL=`48_SXpsc zUX9of4h)Wa#5?nbwaKxm?K3tS!|uZsou+;MZ8M}CtY9F>6?rh#p=&q~sm zSe&DKmmDr#udBB_?Yrdh1=^TyfvD7ZT@bm8K8EB=#G<$@fFKTZjHY^t7VKk21W^<^ zB8m!tAQkV?)UR18l`@6{IWpnx((Pp3o671R4nb2axECu zuE>~&lpRxRj7^)Tw=q|wXCtf8i2Fg>YWw%12#1L2d}_)jDmQ5lSIa_V^1vbAzm`2va*r9LsqLkX!hvW0n%Hk&1 zzWS>b!`CT>DZq+_cQZTruw6dczM1#1Ior()l$6=r)KGMEtT^$BRZPW5AprDn&dIeU zY$Ol^5HV{*czlb?4!Ad&g=9Xh8Sa)s<^e3bra`aG*^HI{I)BC9Ul04%e*b8_Nk0c* zJ)GBK9ndJ^dm~m2oC^lbfgH%m0T0eA0!_!!Gkb=a)*S4NWv1qE+AkEE^K6DX(ja8) znQ{WF%nQ>M*p$?mq`NkG%cZCa<;Be${&GrLAV-tA2L|Ib$$DfSWF+OO7N$=c{Ji$& zxGa@{Mf%bZWb9ig*Cb0y*xcH}bUMW;r=Eg~F1Zk2@CAGEvX{OTr=NZrru7sXhc|Km z{fDu0VLP_%SinDQegfCu`c-Ep?^EbZWCPUBOQ0J)(h3Y`;6e*#+c-f|9d2}d1|U;a^S%pE|?5`A6@4 zo*FPXR_{DJH zB2A|=l0H3S$QAqGc%5O#MrD{Tos?5W+vg--=$%)2GGJmc4N;bvP7c`##mLM-orGsF zM4RV+tirD|!!d_IRbn@^G`~B*@zG?sy=RFbly86l+-jaq5#}K;erw3-b z%Mm(9@oeNZAVVv`w}u49OP6t~Vv3G1B8;WHAI&vhL0X`XKi)%3;+khqxyX@*%JOhJE$@ zdrm{GZ&nO~r&l*{(-5y7NRSKvr5>{wD20t{8wa2a6k8i~IXYIT8h{COZTr{sE(nda zh@{XaiP4OK6R0K+bpa`Z$zwUp;J7zmzOS0&4a`zGQz%I6?qYnX>^Fz~Yua_~o+oU^ z*_E+7TGL*c5BCQBxGSUlG}7!3w-||m3Jw#$;DU=$x|OwVCfqGi^~ZzfU5IfC`bDSu zBuw2C>HO2_@LY*6Vdb7K0+`hSLJ(8rf$;{kZ>5(kL#6slU6fA=Gj8I6BSDj*mI_K^Q&k#3QaSt( z6-Cz><5=l&>iy(eA)#{pS7K7MQOkgU&8RsqoBaJso>4z6eW~T!PPg_A}wU z$JWLs?z`_kY^%26l*OI+bhsCPe&}!TC-=M$??3n<+%dZw6M5{!Hk`Dutu@0JW{l~* zCr=t%ENVy#&Ku1!M=K3>Q`zZEW=yHf9)bnko|)uOLqt9ebsJ+4YpHu&0$6PdAzWW& z%FLC@3TYgy&Zml+n|#vdjh{Gj@cH`(0Nnr2clakf>!Y#e*==!>%dW}%d9^0M*8I+m z490Ey-F8iq;OI|^s7?iN_h?ZakWhA(NL8^*P#hBj7;i4~VPo&pnk2l@QtH)r$Utry ztg`6NbzGT&YhO+FX&ATMN$i6rZ^HLP$0uT@t#r=PG0#RGDR{GHPD9GS`V`n9(Lq*Q z8I%5`*%xhK93(~do(wLZ4>5&s`MTKF#h1xB4~CGyt3r-{Bje1t8YlBiooBw$ok+(K0GVd_OEoj4_E< zcKt20wM_pir%*g*WpgvRhw&rH0j1KKa#&xfkPsVSmZjx`?ZZbuVFn@*g+C~DMAKe- zqZ`v?X7VaKrElMxdNxPXtF~W^QVfk@JMM_$YvYwS{4$o|vAP|su?o> z`hM>p(4CXg+L&!?4DrqVQd!72M_D$?Sq!x7YyODEk9V$y% z(^I#p(b3a2dy_pnC4sSJ5Hr?LE9z2{IjyKHu`6TR6>R46S?+8}a%&MAKv5<@7F!*Y z0{peK^+jKSb&7c0=3gz^6FppCalhfS!uVEkXk77}|H{5jx?G2E8HeJgFNT zYpf<80-^o;9vhgk0D~VQ18e`1!|Hmy_2$Wc0pbf8bV!KXGK&oLgbYK3)!6&Ze8~zN z!Q=r%SGfs=se*o8z%Rsl>)mnn{r8W~;>$1m&=;NMx57pA`nPb^zXq{gAu~?X}4o`6(LVIFlp4sDQv#H1Wx*vt>?o9#kv^sve%$K^as?K#clMI)ADDO%cglb0@&YGM9rRD|o~AW06eXTY( z003v7{baoS+?U~k^IwVGFMK|BopK7crc>N|&wbclZNtvR?bzTHf3x`!Tz~NWxar_u z;S-1c88g&a6n3gHy~EmNMsGni7q5;KHytFxq&x6>khfr zp_Cj~^^A1)Dfw~)SRk2AjMe*%L$Uwqg;iXEbCx|&6izIBRw)i1%z04&}JX994qLcCWqS2u%yZKL-mJC8FI>L+5MG5pgsN>B|pZ6&O^`#{3b8>}DM zaM~94!^p?1jV_`Hd5i|Y={Y7%Fisgg$|Cz97NAJUfa;pA^lc83va;cO$Q!!h{D5p9RTqnmL`JP;~MQy zuXyC=mh6SLn*#<_f1gyiUS|I$<^`0obycx~9K5WI`juWg$_z&X;{_Hd5I|%^7%I*c+eflZYnTyXssLyl4m&p4ic`jgD z2M`gW?`tvZ^J^LGtz6C?-OOSA?kdOj-sK)+!#%U>@x+I4FkAKW&B%1k4CE8o31Dq4 z;Cue$&H(%tfZwXFef7nJR=MCWjg8l$=&WCctq%vfUS!PAd4oY3d1{E_g8z10cJ3OM z*X}^#$`_(2y(B;Pp~uwqOsk1H7cHcUQ_WAN44!_mro0uxQ9k6md`0?3*F$ zL8i`&C1Z#&74l8>q3q|SpV?46=ty{90R-w0aCl<_F-Dwu#+f+hMHk@03toj6owFOK zpMI*sG{nOj8}9ID?&tP}ZD3X5A7-D@d++-YZrpf3KD_adJRGLj#tF8$1#ClwItn#H zPVF`({DVa55i^A{-acoKV4yANU_6@mQ#)?y3Wo%M+6Y&hVA=j1&OyyofMA_3jsvM$ z$FYRa6CTE8*Z>2f38u3s$H>$t@lOo%T4izpPkbfe3W!yxuJsw`+Zdk$WQ7SeDMGTE zKXO#&6Ojh|(Ux7d5Qu=bAxs+p>%f8jCwB42j_qb*4aby#p28hu#OI+0&4v)Wvb#rH zkSI54H9$aX6Q|yRigFMJ-JeMbI_g4PAu8xbMqA z{wk(ms>VX5P-tXp8v+&RFhS+LtF;jo|CTjwhQnXgA$}YV_dq@%LAtnU z8#Qz%lw_#tU9|J2pbR2`0RnrLhl5FCO<^isa!Zg1x=22x@@V&pfK{lD-zx8RP@KZE z${BOxA*Ul`1%uHQEEO5SOggU#LA8VhE_knL{0!>&e?k7=82Q)HZ{3Az*FkK|4q(@{;HRte{*)?RvdDLv1Kvl;+q1biud8f}xZ=WwjZVg_6ezne%V zvr;BMjotrP#>0~lRtxM*camRMrjG4v7kf4j)A5u{!E+x~W{tZBbbgLv%`_>O#>unT z5jYff78J3eWk5DLG!%{Qq3l)5&hZ}<;t$m`{4`c?s`0jSC)js${ivQlY@-#;-aJg$ zE>lZ;wxF0x@}&8&=`e*e=VRHyL}!Do-cf;UFIdlJWo-R6XYicO{fLWoBo@R{XV%q) zv2=JVhv#{d*;`S-G}yvJ#;>~MwQH7M;hg*b;&fVrsEkwfV0+CVHf!|(<9}qp=&qHa zNrULiE(v0U_Y>y{Mg0?Iyb+cDxEB5vhw3|V#_V3a_PrbG-*Xk~^G^nue0WSW3lTs~z-Rk@#|R2V&Dx!t%CTfxkOgQJD$D~_&<9Bk7@7p&9z@33%HbC_ zHoi;_uMDA{g04ylhn9<`H6M> z_^n$Rpt5h-VfUT=K=GE{!s_Z2z+E+P7l1zp@T1k6FTMb^yNsT%k$2lLjkCrULCkcFx4p*(ee{LQq0th`}T65sj-0vbcZOaWNIY?`=U+)2n+!TSv|0!n&lqnZ{ zNPr!YrF`d)P*E(~L=S>Z+Fl!M<0!-!dc>m#%$QTp#sZrf39U##?T-Jw6b@?aT0gxH0)w@4cjU)csic6a)47= zG<9K?Y-Gu~j6(TZwbK`qewiaoc7|w8rcEGxU#HGrL%m04o@&gz7Y4wp8L~7}yw&6F z;h#BZ0^^&IgD_T&CT4{gGahc|573IJy4p5Lb_Ja91S|jtmISbL7iegW75Oq9Pm?eC2k8K@+++0_zQcMdX?1)_inKRai*DC{yNmld!EZFTcLA zpo%?BF}9#v#mZ0EvX>aRg^9a2gm4%^8z8p;)UioU5NA4*z+D1>*S>eN+T3%A$gkEc z%t%ySfF&6V*<%GnPV*WLs#vFk3BUoEq`Z^8n)P$cOXFio@z^tloIZD^=q)h_Qxi zip#l>&m0Lt=?}tOnvhN9J7u{T<*I5ywQhGceuk6k7pFh)XCFQyA?Z7B-2!gi0&ow2 zkJi8q0Db~jUHW_<_3s?rx61R0s>7^79q97C6KPmy*KsfLmGSQxGa`p!R}Os5TVZLp z0Y8G-fePE6BMvYNIaTRSG)S>*n|WtG$(R|w4O);%s+cbX$iXSn7&^Or@MNR=G~ZXL zIU*%EM@n~xi2ojEcl|6@4>X|Fr)~~-+qo4kySWCB>0xJjY}b<=$3X5u%4-^?4ac_{ zUZ;L`YjkbY2>Uj+*nt8dC%TH2^R64r#+FshxS*c zd}oYbI8eTIUan(c#zSrJ#LES3hcJa>l{>As&sfMJ$4x^#ro3paUJDa?W}z^u0WG*m z~dC71Mj^Xz}*4(Z~%TE*DYUF{r#O6K>YV~+y{qKREH@= zpCYz821$4RM_WXc*Em0i5^`x5WB}#LL(Zym00ifb*tM;#MXaEzYkf0`MSxRUX$Fgj zAc|v>y6(L1gb^wTHiM5CW6epmAmJm5e0{j`oxh%*>y@Pn*c-8bwf(@O;6(Gc_pcDR z!C`eh)USKjdjPzry86~U=Zto(n7-}L-7EUz8+URWnCfE5o$;^at!&~_SP%`H^S0ni#eTOXX!B!_|Sz68K% z*dNMj7uzs7XV^Pz4Ac|FIrbFy!PE{z4*y_=FU?OVY{8yxf)d+_Lr2~2nG^Gi?qu#e zs2=hJr##>lMS6phsXI*^LMqhEbW|g0drbq~DqPS=1P>iLtQ|Xd;CU~6J}$c80-pD( zm*Z(qdm6m=I=pc>Y;Mimw#CJ2*U7u^zyl9#ee~W>{N4XK_{YzD=hk1HeEamSI0@u- zw}7*7QZul1!qm;0vpZU2`4RUW=9|O73TzC%H<96_1^7m)Zu525UAN@R&;yKx3~88G zK$QlA$y2SAxAv{Qb8z+X%RF*^Fhp^7Bxsr>nJz1juva#|YU(bg^cTu#-akuTjP73g z!bepc@mDp^v7=P<7nZi(|GgpNL=trfi*uPYE?J8=@z`dorA@l^-0foTXZ`|uuA*G6+WlY% z;YV=g`lqugaQ7;WhdV~x?loccJ)fEZzY5@2tE-pxF!Hq^zd|tv0fACuPV7e9mo?Uy z8erL)?lYelcYB%03Uyt>``2LQ!nb4J+Fy3y(7ctLCE|6A<=Cl$GF#Y=+O|2g4QD z|5IXK99U*^zgL0%qanS(eh^pz&N)EbvL;-4{oe=R3aos=&w19b2l-8iF^URw&UXiQ zU(go}i>!27znWs+){;THXS}Ps8pl~7wwBm9id64T4sJF5ec7TxZjP7(h8YW0 z%o2SwuuWrD_{C;qe%LSvqGKrbbgazQb8La6U#*-cEK`?d>xo2+qIin)c(a?Fbvdtj z;_plK@7J|^KQ3S2#N};+ySg5ZZNst*;P%$1GLuRqsgN~O?%7uAC7IG_hPa)V41rM- z^(q2U5>dlrLhlw_jhUcZ74#uDaUaH%w+DqgK;4VzHxW6j5Mywjh@2440oy;`0xfa4 z8C21{(LB}9DkPaROA-&PlZr((oL?E=wvJJu2*3l=2k|HE7_4tvdZz05rJ5;%&X4&o zGKeJ*Sb_@~6RQoAU=jS2{!WZFy<33zaVlI@&)hF#b$v4d{s#^mX>VfMn+_}!%K-MR zg&MdCz)h2D_xz6-{FU_nD-}ZBckj7kDlxJ&i0aDvIy=~~DT8gat)&R%WPy1NQ$N@s zR##&+S$YMaJ(|TC*)m$qbS` z1K6^`B39RHVEw@c((OH2ReE5Tmpzu(Q183`MyxFVi)wP$_t0@QHhn~dTBEx5y2n`L zM@K6I>B5zAqnq?B&^*|KpfGhVen;MN)|zRlbJ!>7Oc9$PDn!?BmZRvg27cJ3mv8|l zi=aovJ3`b~;`KMa8`m#Y*t;IUW58T$0&R%vmwdQ%{RXV;`B#2%;dklue8src+EXNy zn~q`>yOD=XZ_YB3mB|m8iRJ0wRjWqjUhbZD=D&*k`|FN(7S@RiU}Yn#F1CAYYF8#s zaGaUKIna$U%0$6LDo3cfj&>*7T_y=VemZj;kcaqKz z!RFx3ix3U@-WeZ6;bYpj2TAq6yX^rrYL(d zhdM0|Py$+3XjC9E!Nhw89B{R+n}+y1xNYMWy#3}R7di6jed|_p$gc{kdtM3hUo`9$ zRY;1IIZP0;PrJf+DA(9m+Wn!XcN36zsK#aC%6I-QfF=>Ikpk!;M_rY9HG`#Pz-cD**pJl1MrtP=fDwi zO_b8B$^y zj$YCeDR@-wkm$cNzTx_xE$(~F5TDh#d?(IjsKKKLp^L-8Gl4ll6}v>P#`# zh>eLXjYPz<9MgMu5EF=Hv6MVH56;-x6^WbDvGKlAv74v&7)usU;lX6A(TFm1y1%;_ zBfhJzY}m>mN|MEK+}4-DY@Q>2gu|rF9J;rl;&>^|ScQux<3db#;BTPp|7U8!bSep6M8CH}MM)-dV$47G8h- zNBg~LO@o2N5%;FGZW|+(mmQXY*}k<80{E)xO?$5-{eFcQQc0pYY!;Q-95hdhP03~9 zTieL`QcAJ)dKzLKo^k*3qIiu3L&ucXCl>>%W z8z|tcZix;3g6ymE&Wd6T_*pr32g(0m1MKxHcjyShk;T`uwl%M<6Pn4ScXf#RHoSiQ z-FVx%6I{AJJI2+!aOrxD%g&u(^`;MpH}3s<5BvdulK?h;r`d47ZJX^PB}^jZaC)Yg zEwl)SN|pZ_tl-yi%kJa413Ga5tX*|mPQ?-Kz??NYh#MSF&*^1xV_I3I;*wx4AsmI5 z+E`LE^(~d~u49&J_`Mq6-A8upl8=z%Bvx_8))UKRt&@&8R2ZwlT{>iqOsm+tH0UY} zg@YsALgTjs?>$H$gn+|`4xz4VoPNgXIPd&dVDHk!c*#p%iZjkQ4Ru||t*xmx4j-QE z*tx^`%HzI+4}A0=?)v9H`nym5{o1}uzWjY?g0=CZKl-DK@7Vr<`Vgkrsy5q+QJ-;P zB@il&@e9BTgG3>Y;Ga3Qj|C4Tc^eddLz%98%-uB$1JGzbdue7q>b#SAo3d~rI*wn_ z5gwSIBU*0K95CBDF;2(AWM)FD&ld4udEus(q1F=uiGhkkEkkp3?dD8D+W;~nYjz0w zytflPymu+xuv>VAc`TTx+$07P%fJi!J)P;xI0VvA?cD-_PekFZ;ZXQ_y!obk(#L!9 zQgb$*bCz(+ny`P)a@Y^zo_}xv0*P-Fu^rGRNS{3Wa-4d^E2z{cnJXUp?KRz>7$Yk` zf$&ex;VWlXUVk%ImMZK&%E{jI6;4r%0isWpYQ+3N z6dDx3Naa&0nMu-IcRd_}gsS>7eE)fG!M?RSj*v(=9F@*#DCV+J?cN}jf$42^bcFJ# z1z}y}m%#HZ%&-N|Nk&rE$a7`FI4l8dbbzM5X=wt@6-hW&)%}X`-^VMi|6Q!00uUcD z;IN7atj7+_jQiGp(ODJU+_R7Jz?P zgU$Snjx{41F;JI=119DU&w2PXZAqh%<(XR`5P8r|x~ik$r0NZ|&Ck^kBgwpdvQVEE z9f^&KPzaJ#&k!V#p3YzBTy-J6`)*vj=jVg?DQ|vcoNR5(myw zw$?hVe)l&-Jjl<0F-^l5J0(Zw`#6lRb91ri{8IU zG1gKWJV6r=8Aie-xT7 z6fw19>=fpO8A}+TnCPjCFlI*M8dywbga?}f2f$KS3o7)$Z-!RRH!`8)*l8ztvvIJM z6b{L19K|+<4MD*S)A;CP==G2TJdUn0=(M{8yR?vq!-o%JHk)zht{r&sOJ1al_gsW? z&wUx5@|3554sp6QjrZMqZ?$8`u4?D5op|8T!Q1Y*=QB6n{+YXe>o?Ya=f*c){)XGy zd*puXBR{tIfp_0cd;rpg2=Iijo4j>7v;bp<@a3Oo#K#~}TIV$$_RmNoDX>wjEJ9~S z#9SGzb~s?d*uD0lqgH%_e)1F~3TyFPpQQ0{qU(F7J1w#A}5 zMn?QBk`ThVmN?OK2Qt=EF`!B2$|$`6SvRYl3SJ&BVA5{gu# z63FjIej}N$t$FL1)o+WN2e;rB5b=&W&b|v*yr-EIuPjwKXT71$?_Uqup}jh1S|9+t zd4`pXp6)9DC8}!ys#;_9ffp-f_Hi=>&*j?1<7`xjqyj2ek@!SF{pI2NZ~9={DAdQq ztM1p=0I)+9E7-T}GM|q95LTOhuq)0=+YAx~Ll)U^1?KnXE?F4@(@uGgAR*knCakPD z7vK3WB4YD-qnd!!!mv&AmuWF*>SxrsG+cyJq$F0AV$`?CD{uVWbl;EVs{=Dvma1^Y z`VIc-rGJOY{ko=MTQkiryO8n2sZ4#%ByMJYDCV$XcA1LArH5YC#zrwA8UUNaB9b{Q zlm%3j-jxpXw{#bz{@yF8en1tC@aVfMx9{KWF9InC-D8Mht8W zgkf@PR=}x1SQ92jY;8M&d<_mn#2fzTIapXYk0AuWIjcO$20SB7zkxwG^E%5y0D{1% zK<_4sxG5eEUym!-?`v6n#~wgwBvn;?=?!mjS1(OixvRBR&l-@ZPwbK@1yUXoat33^ zjI+tpy|ssO`n^k9X!g_l(d$pFLKSwrNYIM_&GNs^B%LjXfCg|bQ1+t6&3!&29Vdvm zHS+Lfc4n$Ou;%%ke5p4S5 z5U^04;dU50dujL_jkeMMJA$lHde*C!{FCybhbw!Tx{&_j*(e9%0xJ@ixR z9@#QMX~qCor`eoa~w{5Mvu%L{B;A)dzVI zbk2OQnxtDZ6{<;A2N@CUEewQpR-mwx$|=(N*M4fox9Pdt2hqna!%F2K93Pu+9Jdq4P( zfA`wode@)sdCC)>^wsCS;Pd~_8!vyuZP)Ai>TPd(+hp(l{qCl}zN!A(&%Z5Pv~^KL zXl75&qy?vYYOZNYaByvLna1VJPWaXuA2xfYsSlhfMyvr06Ij_um)WNXDo@3t0X11} z^X*_yCJLIS0i&^F=;{@k=Y*N-d)qyADQkjeU6H>3c*U_bXq1vf)~v@SfwTKu9LH@v z1P{?sbS-1Qh&li4cr8>J%563%QIczu{t!)CZb+btRC=qSXAizmwn(M&)Rsf@dTW}Ku{qK5u*H__+_k6T5 zHwBw%F+yuNqz5AvG}GbbhKW;6E~fBQL`^BMBL? zt?`QWUwu@-s|?6$WDDVn^*>VJ_q)l&cULu&4VJTPLo*^=VnnEw>dQ6zOtXM(r%1chg8Un6c_D8*r_M#=AG>0mc%fvgGnqrp+*rl1-+A;UM?rUE{5okVo zyBD>C3q}OD=q7}IJ-9=c)>pjiMqCHqqPXt+)Q$A%$L4bh8PrEcVEMNCE6GYHZGes zh@u-gf@w?bhF)s-=5-Ud` z{&&3oeRp7a**!)KCE=U{#OnL+2g8rKgt*y&B^I@>$&_H1o~ulDE>j1!JyaJYdJ$IO zjvCZ|3|=R$gix-cRJFhl^MP-hpCoWZ;yS3;`}kz`H#C9cE}P;edsP;xQFDeVgCfs( zb~KG(?xB_Yc?^Sq`+2c4rj1RZY1s*~6Rqs0(E=#FbkG8Dmepgum@-+6l+)=4jS%x9 z&rtGR&kPCxxrUUJFBT3Wgopa1#iVArmlm~L%_jl+jUxXG?vyS#T5 z?mcw>M?QYnZSVMh|M-u;^|k-`%l`tmVhaFPu3xG4?%j(Ua6`Od{|ylU&N^^_H~{SW z4}UMg?Fs5hEnIv72VAySt@Z?>e z9WYjmi>lg><@#n@R{H0xdY0{0Iy{}zq)_8z*Oobg5aM)xG3uPn?`n<0)thl6HMmsC zUcjibcGQtRo%0G@^bb<-cnKOEU#5d=6Eg*2MCxhQhY{|x z2bsHxyCZm1A9Aa9*gj!7{cGg6!{u>A%PmXew5I%4Ml}GQVLbPcCov17HedvM>>kL|F|Das+n86;`3y4>NEbN$NFH_rC0{p}<1 z#cKTOn$0{jxwlU!hbk4#0oaMsIsrM|ObA@8CaB}D)OURHoABeUAJD!>5AfaOELL%$ z#nzaplrrFb&aN4#L#)Kpzp`(O&`W}%N|^P^!KK{>k-$%hg&wj*K6BLxZux69j0who7}7H zgQ+5ep<8oOVSqP!B_FvnJecxY!}C5{lFZw zsr`57#{t}yDO7D%FA(Dk08Qaqc0qYF({0e$ALSm=c|Q?BH^G)Z5x1&eYo4LCh{sB) zam!jmdfY$zL;A`8Be`3^41h0nH#nLNhBCu5_|*>tJQ#z+?6Vx!u?Na9r!BGLmlXo_52SM-fvU6XG?|$un!B76gkKqk(_&)5}bAGI$Bfs-g7rgw1pa0sg{gN-gi67hC=D@HHv4(#MEa1?`I8I% z8+BA;5z{_B`7kE|T7eZJ^uG$fxz94r@ip(ENgEn=E2MU+-TJp)iu z7Co5Ql;UXjC(ABs{pTJ&h(a?vQof#xf}>;2*$Jo0&PFl9u@d-q^`ZFf`0+p5!u}OL zB6&h-q z3VF?FE||Kc>S7(!lo9BFe}|Uc77c5lq@tEwal`j>?Zno}VQaPt=oQiF_p6nQFKPC< zrH4Q8KIbfDG0tN)LTnuY2AjC@$x~VU3?>%@A_6N;6v9pQeY0=FkKH=Ob;o8%am3fe z33S0S^qKbe+~$F{7MZi~G_MrXgCwJ_B4TBwilKf{jK?zHS^z$=mPEOU11G!*Z+hn) zSbNOaSIzp`58;L-kJb0y?dbj#6OS%w!4gy8+C4W~HZU@k4$|l9R2AvhIE5JDc=l#p zzW@NRf91F|Q#o+~thCl*H@Q#$;_uhA(X7yChd;W9ujo!;n zTvrpfkID^l*dL}D@uXll^z8CE9Ya<(=Zj7#D(?KrqH5p#p#8_+b069#Xd;7^Y!&z`6EVV8$4rp?3EZsw};2NWpXv zW&jV{_mI7O%*P311A`2{XU?SSq~C_gl-Y1+0|tT4*O-PMz#HFnTQjYEggz1Np+4~^ zzmVQP1F^2vj;m#bZ7Lc7X>29V8J;vz)g;Ie55=wc2CN>K;+(VQ8_OT7atUZWk@hb+ zy#A)UK)!{}(H2BQKp;0mAV9Ly+_ zaiQ2-fzqhe-Wm$dyEziV{j0*tiVLxRSsUuY;+V`$&9aF>f068@)Skz5S*l0Cr1FUP ziF$SYriMxK82VAHu0|{`dt7n-MlaC@2dINKd{DAk=l54i4g-aTYJf554CB>|bh4ogd}b;Yn1K%HcMwq|lt5ece%v zDYB}({aG~j6ZF6=j2{{+?ZsL6v*&rwXH&86NW3h9X;So1dvUY!F?Pp2^LU00>wWySz2FUU|>E5aZ9g$)tkNpl>ne`g@^Cwv+k%-35ut!BjE> z{HP?Vve#&&9%wIC(9=Xd1Bzi7E~2clQ(1;0f6lw#P3ytbluZE>?*aZAuDtQDu<|I% zHjh-FoO712x;7Q!k3yO?b)T|EHhyKRfDmR)s8kEnW=7&hHd!97 zDK(~*zbVF+Nh@nW-&G$XfLT4m!Obl=5@$cPgTnOS^w7>?Y?qPXuL++_NbnXGLzQ1`7MuydaSUUxFFz!if+|Kbo6X;MEK@<-OW**x>c!bqSs<{x>$^fbDen`= zT_Qn|%#)~C7+on{Gh??clfBhi(IR?|PC*Ibns+{lwLVWF3hcCog&}oPbCFVh)@Lbq z8lE7{Ssg{k3bXi+vw*iYf4CkW^ZnzxwTS(2@qvSHcPH)o8dUzJ2z3pqu_FTwC`kL* zrh6fy$fg~C7iolvp#}%<8IOuKU%Vq;y8a6fOHR0ZP2|NrGl_E=L!?lLJcYpwm#h@5Yid&>c(WHodbgm+H)8ds z11XvH(KJ_XgC&4zBo~xIdTc?`m3W25T}4c{#pt^IvvdJEVnB2&vG%}jICnY}rjRBw z?D{@H#7K+?A?6+!RMBOzkQ_;YlWL+6-+?RNeG^vj*a9fst6?EC`00=Ou=sC4J`q4{ zM3SzrwVM)Kh@!+ns&bxII8uX~Y{u4?u=$R&4{~t*C)zE=CkmL-$s=0K%G$cn0Gi7D zTdjJc^DJxA@4BbfG3XFy`Z)j`3|StNA{+wN0#@L{NBMu>If z(e{^Sk7_~seqS=@XR^d0m?yKlQSf%(TEO@3c>=wAk?NqvQPbQ+*OgzOMOGtOb|5r)RD=31(^OyAO^e%PNM7 zx|=R`bi6{V$l__%NMq6;PC4T#m!TQGiSZ@K(1|RXC0m@O4~;PiL?N+=Y780C{C7vA zpiMx`5*3T|wrnLkHm;Ru7*}+SZEK7H8=IS$&1N|5lvDB2^Iw9cOZMPpFMk=%e8QOu zp^lrIo7&vks!lrTqzQZ-x5D(6yKcYdk3Mto{@?qKE5G%9?_GQEMh98}aQpY~7s&`b z%xM}2y!@Q=Ke~4fy{`soX2eMGG2&-1rn1UnvU*{>XpM1-M9Lzdh$C=Tm$+A^Ip;BF zHU*^*RY{f}Y~fwD&D+|6;}^(s10=UV2gVHW>=anJN>IJQNoNinv+>*s`RZux6%n7P zt~cu)*v;0WK4ieGOQX-c7s>j@NvzPhRMf zgH&bj}QfjUP5?vHh$x^jcZ>ih3^*DU=SCjP}S)Sa&lh6!{do0NutKqi;(&O}9r zTBzvGnZ5SKJF&cWxCcHDbVx~n4Q;W5oRQFB6-|oWGm8ASc3W;FBe4VVmksE;e}%xR zsu`X|^2~r(W0Zub+>bCzUPG3E#NsJr&t9wP-6El1!<9GQ-Wc>g4oxBQr$lPN+y!oqAo)7d=c&3QhcW4TXv@*V;@+9cc{%8^I>W^u%iC{IO1kshcV zr0+nYCY3k!{Bu}YalmSP?0j1mVv`qZ{7g+FNHgf61>wvAVeYfW86ao`C$p_1 zFYQ_Nd`wyiNTFO!)|Ki3PLwOu)SoINUAx08fUFfl)K6OQ<}rV*qG5>sz}l_2>f&Ex zRsC1ZW-~eZe$du=yns14*MnR6|29M8;xOyjb>zr^w`B{{x#!A(2x`yp#(=t z)ZZxCMC>w|Txl>JKrTS|i`sqq=H_f%d+Y}rsSa8uR@XM&RhRs>fF(g9DMd6uxa)RO z<@iu*L7(%@w)h;P2rc6nbc0ejZqM3VJt2#-~}&uJ}$iQe7x#auf)@z@ichnGk5>`Z@l+U*57jZ>+b7od->*Up&mA9HKzza`)y}SytRay z2ZWB2RiF{aWqMG4Sb< zA~IS;M=@q*ub~W|H|>$S=8`ElEk#e;Mvq=;D+WcAERP#-&(}V?ghp8!cKW%Jzi+L;%Mga z7Hmm53kIa_3J=!`A1Xg#)cflv-1k@J{XfR5vSI$zoBD09`gNc?sRaRPK^Z&Q>``VU zwrb-*3d%!i@@kd%4f_K@@$+!%DQAKBN36i*pV!6qXExsUU0SROt;op&N~xHsK#OYG zH0JA6YtGC0MdUdRh*pD7^-en#um!^BQhye0VUf;}fi_Hr+Nrr^0x3L#rgfP85daR% zj6Ufvu-NGI1|u>}nnMgxtKi^WhUmaIcQVr=$D4^0A-Dpsa)p z`K=$}7o+EFn%TP*xHL~2YD@XNpMdUwFBT5obz4Z zsvM>1>wr!rqv0F{Mz)zH6C$|DWZ^5~>P7beF1GYl!J|tZ?F0c`olPJ*FnUFLc;6Nv zoWf}^5z6~^OPQ59Fzr;hr#O{H zn?&IuP`s0pn#aNWFb4#g9oj6W$Ch%;j6QDA`A+NIA=upm>Ier1=kkASZ6~$4U}f=b zy#~~n9y(7{Zo+yNaQN^cDGEfAbq(_Z44vz#ObT)lB3u-;kg8sZU8S>22m6dZT(}4Tk1W zor;ONau1ed-J0m%?Ji9tgOVp0jVdaw6J{gJBKuDtm$&RO=rF(~%ZXF7Qo>-c22j-j5Wx-3tnJ{X!KH1!s^ z%2I9HilTCeF|6bAn`ijcQU$EnkNG-UZCiqUpZ=(O$yqnYikE6yM-W*eVY2LT{Ui_u ztj~txUE-2qhqh}n(&5x<7S5=Fe{56#Oczl{$hY^P=MwaQ5RxCe)MW_{t*G+D4~9+H zwP~~Av;Py#l-(&lD=|lsBo`*Vwmt1lf!D3(on3uCf<95IMp`)j9Cm<0r*Cov+U+l z1iCdI0bFJFIY}z~50b9hfV!pDniwQ}%GAz+r&H8@eloc}0_>XV$LzHbNL8)`O=b7D zChO)&wECf;UPS=9VB%e$hN&vlmb>r!5Y9a909vU|VYt-KdHryZe1Q7KsK$k%H=J(xV{4rbhn-=_2L5Ds;MZ@cEx0;A#Vm@Zeg6#Yz z+q|f!$hn_s6hUTs)54dEW!p#qo{(cJY@y(r9X=v>DxyZ$eLKQYYMd|?`j`*+3RE(! zbD#%fi0X9ap}oe=P=syaP1rgoJ0a5d^oy8)A}hDkGd`&X{=Q3J{v3dHY!IyIn7acp zlX3SOv5Y~s%<3fy3z32)+glPDu~i{cvOKR$nxGLx&H5qPNUBn5P{zfN99L;bgCZN& zAgoOdMR@1nU6Uv3orA9&y!UY4r=*dVrKK2~$B1Z$2kL7P=Nx?H5o5&Rjl-DDYMiud z7cRK)Ram;@V!ra+m+Q%vNbMqs&efq9H{={v!{mwPN z`iA$e|K$2X0Ju)qd8`4WzE;QhwfCHJBz(9x<5O|3im5#~7!H^N4HIn$;DpLK`UuSh}}C8sg(lB^rgGcqYOUb;>n2OzA? z0gbj$VWDl!B8sM^A12VS2GR=c3lKK8p|mDQY3K@wsDnWNu48&Z_jga$;>mFF6vY_G zwx}J9X)Qp5f%KdZ=-MZ@YmU3ZPx1^99sfE2z=5+KzX{m>C6BfBX?*eNH^EOX1sbqh zC1ba{7iQB`7gD0~IAlIFXl`UAU8M=1Ft<5o4l)$nhW_77_*~~RWRV7A|oP1DgjI5{-4 zh0qqL-nmFFBIry?&xnkl=bd{l;GZkU9jGG|%#08B@X%=E}#Y_rrP!55-fTx)2afV?>4= zFmFsVIUxqaA@r;h1HAWVa3li21^*sp1F6UHwE~CLzqvQAUi?9N_X^Y@0`!@!N*TW1 zJp;_wWir_xvHngqh-=UKv$qe=;}FgQed+L$<$Cm_1R~FV?&nqmETwBINGirU3Lk60 zo5x)xA*{gRw!ip{d&b^>660=!*8OFi-BEllv#~N?lPVDSxy;nx z(!Nvl=6$bao4|+xSGPG7Vw{BtMkYJsLSto)R7K02p&LecEb=OQ%En< z__K|PXat%N>AKJtIty0ymMY1UG$T{kTAG=9#e|0il9M=Dc*^nG*POTj)}m9trkL|{ zJGN4EEOUFQp4F$JDSr+wl`U@~eB`8a6{f?Z53G^7*u)rSChU0ug`!Ren9Cb!c1Zj8 z+F1h~)ubl6K1IyAq*3WJPonWnmK58jBsPn?swzea)9Dmj2e+`eZ4u9X&a-v?1+T*S z=e-J__pE1uj&XBiBi?)818)0{?bS&qpM(bvAG+f+_kHI2d+xvIH-F=If8$-N-@AId zxyF7!_G=&aJ!X@eH8daxTkmQWo?^{|9+6YG(Mvmu!X!1zmi427@8h6RGbCBsC6gF` z$X$Ijj50|88_RWKCcC59RW{*?J%6!_K@lJiH3c?~dkJ9&dZ=f1&@!r+So8&=Jj^xD zm;ob(IVa|;nM9M|o)BV|w=k&6lv_Tg6MbBZnq8ypsDv8L8@ zaR0OgvIJn67>W7jDN(K>cy}G^kD2RnOe$tz6_W*jvVaA}7?s?Bm|vGJ#;jwQIT>PY zL}`@Qf0ihaUSW1$jN$(^#p>GQ_dK@XS*Y;=-y{Gyw8|DsKkui>#WPg&1$4s$Sp9}u z$?*)cPKA}--SAr(K!4=Q7(-2=oy0cNV4`Eym_Kx_j{43!4W!a_#_kC%%1v9eW|e{c z{IM(qIt#I!HLJnuaUX21{qZyXoN)42F~)#~f7Mb5G>8-EC88sY=8?ovJow!1Ap%%V zScnn53b3{NpL2|uCcjjmG7YktbrPG1LX4}`6c{|;9FY4fqV_Vt_goQ5f$SE2es zIQJSj_xTDT#;8CSeHtR52lP8hQer?va3p#$8N`=+eM)g9AbJ6sK>A*+>|O_UKPVeC zqW5*Pi$E&O?(Lg#Wu~?bKPW;)5F_cq3J2m3z2|{PIoM)+kTDI!#3RNo(Vj&*gNBTz zOij_Uh8;BMK_nowtmx>0S)3m(vF(|=5aX#3gDGkTdr#&irjZaL3%s+;OQef2Y{bX| zkNcUF&ZcI6B=k3oU6M>6R*)b>73YSm%4InG9A26BE5UK(`bS)L?u5kY9m}N=y!k#z zqV!kh_?pPfwN(QB@bvyO2-Dm1GwJbsEjhe>eWt6!ZE~*p;I2cLOKace;4MV--SsFk zH!{DfE=^#*J|yxiK%i$&UMZ_o6`>fgM1C2E9*#UdO|$1byj1)oR~}mJ0qF8!iS_D~ zV*=xQZBV-^9C9^HZ)?(ATvtH+&PJVD*KK79%~np{Bo7wKu9yiA9Ri;8IRNWezqY*E zm`roh+2jV70qC@Q6U*twNd0S>QkJZ2%oztBNaA4bC<~yeXVI`ghN+=65y>wBz4uIZ zmbXugVpy6KiKSvRWQ3xRdG4@~xI^$}LU}_Kv$q^8gLlSnk0&Ibt zrl#gV>o)uT&x-hO8L6D|WIhdB!r};KAz#Rlc-2)neu1kP#LhIS^ImFkSkg*eb6mN6 zJ_laFAJRy_xHGoN3_H%Jd%;MC=WNGww?r5v2p8 z-?O`8(t)INO&lUK%q)qxyI>0%K(e1k7JF>L@(BbgM^ye3xO4N4w$?kQ>ss~nGG zTf5JuA!twBFT9DGEi&Nryh&-K z$GCW!^2{+9pQ0rBwtjBu61eX2^b=|hKx7gTa&)&gg9JSO_*MJ+ry+jaK^t^#vEdG> zEk^ncgm#SHGk~lGVLzT>j*&CT*^lylZmT2VqO#2BwrF>SB*^$ds+cq;d@^g@8*}1r zqK&QR?8M49OmV{v4rh(9BbNa@;ZEWP05^=hSNlE7BiDPm7)E^mP4@tJUk$tuZ$9^b zRN>@rBI6H4$1@Q%Ym&DzOYj^xQ9n4%?GXAeX0>w8B>)_pW-&PLm%PrpIIOJx8CF&t zR##)T;q5PI>CAh&9H_Tb3K=#Y3UFT-OT{t9UCp!7%H|)_XJcVI5sP4K*UyL5Vi@cs zV1pyHVd0MMTDI1dhLar&r0obhD1TBjyqN<651Ar^p&mPBh%>*vK|e*o1H2Pq_RsC_ z*5i($wEH8S|1cG|BIrR=#D%O}X_vaX62&xq;y+P9(AUi zt(iyN4ngzfwH7B2XEd9$>sTD-;EBfCrI-31085}!womM zy?ghDHbwApdwuxJ&ZcI6@A_85q(0Hedjv$|b)89=vV-0<(NwlM(jr5}DkFwdW{ngV zgLJQ0Zlc5SWlokc%p~T`b+gwwlCh|WM2eBqb~oRD{5|)%=1El4%XJCfPV`^xThXyi8MC7r_B`H8Sm1hEk85?nEZZ?;6Uz0M^aoy9^W7 z+9_`;TgS}dQe~1Yv%-aPR=b{GoYptXNijr++ZyoVIAFqdM$J=wnv?i|fE_JN+{4U8 zAPSXdy;9w0M+W!iDZZ&$d}Q@PCtGa?ow*=Q*-1=efNtMIuBcY70~I1R>VP+|MvPl) zH6WBgTsj{EF9E_a&@cywRtVhS@VfQ;YT&I{z2qkE_zUoQp@L>=cyYtd*^JpG>us9* zap?%5MD1e~SaE_nehq*>>-NIY*M1O;spHR#*zLBMi6wGJyAgg+0cf>w_$=J{xIG)w zKula$;@Sis$SHRsWhVeCjx-iNtOX%7>3}*9tdXm}h;3EP+9||-$df{t3g7q)n95BG zWtg?yif`0%&giPmQ>n4X$c~y-OL+=r) zpk3z;5$dk(4(^acyUF##<$Qn1YSi+aX#HUn=U9L=1?m(FJbs*NKYXz9hIE>$Rs1Bz zo1)Rj_BCEtA~=)KBg;r*?jM~xEV3RPoP==Mxf6gV%l2}E@B}1(e`IG4jFGD{qDp?t zbMrodWCmU(G>AgffO4LE9!ntJN{Pwb!w80&mZNVfl5#U#_Rvi;`HsWQ*|p_M<&IzY zrcy*eGQ`CwBxpafQA|j%VKlHXPA&u=PCQSn7qPq+aUHz-u=}^_PflcMVYZTr?O$Ss zt5|6??OD%a(XwkgE9WD4D?YL96B+O^c!w^a>28|R#8dWW$d1P}EFKCIgKX;5`n)s{ zXVeDPQDZ;cu?Bzyp2?G5_tjz@*=QhRqh;LH*KpI!4#8C13ndOg^X=oKI>zK z8?OCk*FxAg6szpXy9r={Qf|gvHY9}{#8{XNhZe1zFp+2-8HFuv#rrz+@Hp5}+Wj|d zk^V3ww*KWwC_xKYs+%0o10iUmMf%W_?(FqPxz_7QEV^{$DuOoDWXzt_x)=4s@;*$x z3Bu{3wbV_vBTn6j58=A$7~TT`t4e*?+t00V`Hg=OR`z^_pZIss;du(NM(2zoWky)7 zT&v4NFCXqRLPf&tXQqv2Mw~mgUp12RD0gCIE}ak45XJcD1Gl3NE7Y_)BLVo_F(jo0=uQTVVWH_p4=V;$xjxnI{&1D+8 z<#n5qH-fiTm&N9#fj?MEw$FTwNBTWOO5l+;M` zQsfTOaLlx(vs!^5gox;fsE8T?vrxAztmf~!%A=Z0Fx{Nu@N@&)c5KHv=e!UXTzDQ{ z@rsw@=}&*U7A6aEbK@`$A3p3B7Zw$Z2`sdqk|DC`8hmZWpH+|LD ze#C-SYinzGyd;7Ty2tw98Db?Cz@-5ybEcI6s0;|g5@4k9h$y12hQ)G!150a=v{9;0 z3z9*}6X2SyS7oGvJjp~u7D6$$uv3n-mYnNN+>fVMw}I9%f*zJyoJ|X%5k`x0ONuTo zdhq1S6pe-2>qL4jFFSB8Y+tXm-V?Ti!;wUb0attn78&Ftk!8AG2|w?&iz0R6;P z0F@`)wPv+W$@#87Ph+wi$xV}OwO0N*i>Q_NiD;%j0Pr8r5^g|0WxxMqk5jZj49sB$ zgOI{f-ln=IIgyNZt_M#D06~6w9Ol8QmUC0Yn6wb-gE{0B@v>FZSPHHMsEyUTj&i+M z3vB2Mrc5J7_BR>{YOkHnlM#oYxoP6{xSkD!fSYHy?A!@fZ~8D-FZmWZ{2r(sXd=)_ zmxXnvCxa=Nhy(9Hp^s!&GKlMI1~s3&#sZ&QNB`vjejNLk{JeD3NQnb79dqEi0P=Hy zio$MyPl4L2b zEyvDc^dKIObRyjDDxSJhm=MLY7WVj9gTY7@!VHGy8x_ofj1(!L%olL z@xSU44Pa+{{l<|q{f0GFekv7HU0ck^#-q<+duB%O8mVuYaScXBOh-5{%i^%x8JZ*| z#fEIuh0b(oj4=k>fByq;j(XZto`yXaU&N)QJ$k_lpNH++w`+5AGi+{dIz&x&?A(RJ z8ykln*xdZ!y?5RI2OquTD#NNPj_cM_^qh81Hz`%8UtiN5R?2h0sK;S zf-^VLOjnY|9H3{e!Z8H6pm&7_o7zMQUJ;}(w$$NXSd!#ozK>etYn9bM`KaAc2VcpQ zqFqViFyuXseV+NP5N(Rx@daX>wp}1+;{(i;9hn7N_PCHcg1Nf6`A3WZEiwX2$&DE( zbRAirB4oZtZ3i=I7Sotw0cfc6%g9Jj8UuMdFDK@WN6Z?P^a! zWvQxH*56fKwe-VWShy-~PG=w-R2J`@qn{#2=JJ7R zd!G5&1WYuwN|Yw^8CX;A;6m?@`yiDZlhJiv_i7H1)>-V3VmuxTrG*wN8F&MsuW#Dg zrUN-hb4ICbyf^|ub|3@#6vDOk54H{oZXpg%iL8dnS8u7h7%< zw*zhnMrmv|X^J19Xg zjU}bMgfbL!t#mWG9T``lZVgi#2BD1vja9f&wVsKj~be!JDaWjp=s))s7S^tII% zFmUS<5~Q8F9bahQw(YjHyKcYQt?d8;$z?`~BA7S<3Ialagd~+z-E;O{&+m^leb?Hj zGE|bP8_3~Pq*A$c?>T#~z1H{pe4k-oH=nwBIPd)PHoYIlljHb@ANtVye)nzfe9s?z z)i-|QpWxl^KAEl$ues)$;hIY?4d>d|>TV5ZZr&#z>{@-Z@y)SSZDT0dA?#XPk+iZs zqpoj~MU;mWnen3D*bsA5H_m#7)^M=d=QL+)%_N2E{Gm>>4iDsmuc-#l(#Ui>d97(m zW}sjBtO09j6b?qWdp~<^Z#x{_nHJCg($9rkeO(yGm7|6Xg35s1rCBTS+C~P}$4a{z z%nP-R9}{{-auD#rXGJo)CNXHH>&Q$)+>E4+l{; zDXrT_L-CI}%|Jx1@uskr8EYn!Wk>G`GkMynzO0QMv3FDh!fq!J>#+cCjanPBnMR>i z+?wyXt~t+jNsfYbG=9_qgaMnI2e3xlaDY5p$}#JH(5Fsz1%42VzgWW zG0p6h)?#onR&_ASIdutg2G*+QHJxzY0g>7t+ncZhj??nRUW;#scTWJqRHqDV%LXSQ zWN-?NWQKQuxWw{85s4lH0`$4~vVOXZE0p%)_v}6EXRy=QJLgiSSua7ZqAu% zkiqp{N?qz*yP3AE6~$Az+$^_=r(ZnaCD$IP;mQW~hY5>8WV6h2?cAon+vUc}_UlQP zXwQ}AGQRg@3@o@lf7Rwv3j4?c-g$aRXKLPp=sf`@%0byVF)2uR5Jn!3&pyum)HR)v zUhbh;xEYFFj@gV23Af58Z9?Rly{G1^FnLGEuS~MVYGVIzkf*YLWy2b6Ku5p?V5P%? zZ2wH%9%F`7^D)*P5OGVW6dNE;Gata549GkBZtbwE`#CcjOkWGB`EX_cEtou+#`_B; z%J*Qt9PY2L5AVBN>X(f_9dwdwDd}Qgpq8erH$*SZ1mnRfyPQ-bZ=0kDs?h)Kd&xhi z{OXsc>Uf&QJFZ2Z+5B*(U$RMc>HQ+n#o-7H4sfx+_5=p+ApsA6_(SlEzwjUM@BZBj z^;KW}6*_$Q@buvi-pDYnHWnKjn=4Hpe(#MR_-}9d>%aJhpZw4N`Aa_ev7fl}RZn@! z>-g?>pOoY!IRd!svdhMu`w@5r0C?mhVLe%khZ1YUYlSd&31yZRtyzAvGtB!in7T02 zKFJEK%G^4dX++uNRU@qPrrXydWT$dKclb8ibJN}%c0y(7WxF!r0M2#{>|UKxWs`FI zGthv*Bs#fd@_;kM4fSSvK?i#CK7BEf4917?St1_-C{%wdg3dFq<*M^nsZC2Wp*4V? z)({gykV1fn34RkycUZ2_YwjXk_d)%b%8RG2x&gRVb8Tb=` zWsTG+a70;8N^7iP;ufGrWH3)?L+(JHP{4tQv2{SsYC_n&*#S7z>_1&(wfPL{SsiWX zS%*m07BU@+ca2d|HUdxvlvNoaB~MG`GrR8PZ9muU<);NtZrK#+rrBAoc3{PxC(FK$ z?7Wx*jAw>KVgUSk2WDR>m*&(V01r~Ws7WWmO57x=*ti&vv9+uh)4O2;WGdL$G2QF@VTgc>O~fxyqr8K(@aslmZ232 zRu9Ws2NmB8;@pnowQ@N(Q|tT7S~bdEUl@{*$=}Tq|9FO&M-9Y7?qC@VZWS+T;OQwa zrUqIwz-+qsrjByu?*)bjV>r2aVD_f#w_+*cHXcN}Lr@4=X_48iwpOrOh#Cge0`T@3 zfYC!J-(7bf=xR%Wgl;yAWGRFd&`vj=6Z&uQ1E7%eb>7_MRu&_|vOWiO~z1!hdRyzhjJ z8ne`rqQGFibg*>z3X zf3`KVCQ+f}%o@N<9Xw{daSKDyMUZ;y0RZdXE218H*d<~dSUyNvh=~@MM}kx+Ayw^$ z9{*c6x33SD`Cnd1!KBp`SzZC3>Ly1w-<&EqCNzkZOwlk~lxjdWnS)2RqjLZT3JV8q zcwp1RFFd>-u*m@nA01H-9X_l_Kjven+fLjXPThXepLh7s1{QAhp_3&Le|j_ywW z1xL0>eS<>S2N8YWV&l+i?1&O*zCG2aA#NDZ*e7NNFic%{!r6q)6@G-mV! z5r_xjW4~%R0B&sY9h~=&Ln0m=S3>ejwVBLz>dfjDM49}{W_V=xNJi7`xFT;~|1PLj zj)8KPp4PH1fwE<-gw_JiCbpK5QaH^ycjI9JZaMRYA*+;}?XUQj)eb_+Fv_g)Vz0?S z=lsS(kBp%7*16x0k8U{t7#98!03IR~f|L{sp31h;95=2~wWhppu9F2F;mW>YxP0_ohArwVrO9Sd_E64vSd^#B2`;Vsz)Q zpBWvQU?-!?_2(e_ldH=MZbIEb35XR&Ve46buUQ1^h zWP9vq$&q~~OLgfOJqM=nF?i%d&I52TDEq0o7Tsgu+$M+#&M}3cSQZb^0lLhJ^uj<4 zMn;@3hAZP10)X$X??n)H;qZOGzl7t>HXNk(eWsdOw=OKvK>|HR&t&{Sa{z}x?*>Qz zDG|K8{@k7oS)>0cAM-I`(kCf|bReJ^#@7KtHAjyrorx^sOp%f5^fG`7iYjL6^fTqR z?@x+J+XkFf0?y|?Ship(r3&VP3_GNQU;wVx_`DKX(~yA9oH)$&cj?>fDcFWAID5BM zhsKRpn!yKb|B~mZg`m^%GMj;6A@{JgqU>sJ2;)I*ckaIoEUa9z=BvZr^nn~&eY{RL zwCw;~H8&7X|6^jitG!w3KS#}Vd65T2? zusFDAIxk>~NXO)y!!T@axWk7wJ~ZC;u0MJA+g|+g>tFwskN?=me$6GH^?5(`%%k7& zR!Q!-j(bVuWtWX41MjDxm3=*UW1sV|iTc0N9W@(FT`ZybTD8d_{c5sP6;lMNpLnfF zq>(H@U??4IH_N)AvB*yBO$x~iIoP$Cr{dX+)BF@C<$diy%jPDR#w;{+_wp=SpR#pR z&}Oi)hUKF&=qh{1-=DaD$Ot9k=x)G|J>?VV-H&M!sVPz~OD)$iLxTm$*Z9=*IRG@w z5=Ebn`kZiJ(220X@5VpayJPxV5EVkqAtpfIF=Ny5sR1I2D3KWqPGk6nMa|l2fKlXyhoqQ0wZRwm1kIp~;F$5lgF(h}K zZaCC`y#?@!YR+CH*Kk@RVjlp^{}8 zRBe9d?+17+jaIt?xz=b_U9my4m(QVDw8I1ej&1}#{=n=nsYwiUpA5&tm=wy*H3)O;!RiI&*8|PgWuGN>Q67}h+ghLXHG@i5l4!FTHi-wXc>|g0e9Ls3 zh}+;DVdvyrzdzF+lnv_KK7&Od9pB1G;1L7(2^QEs5G7EUK0YW673Ao>_A0nh>T!jx z$6_bFQZtx}K=zuCd|k4Bbo>zLize2}=?ZX3do*%wa!#pd=K*o?U97_ANAT>TbKIM4 z699%yeJX^HSI`O+YXYIx+O4n*eX<|bjAU=1nP#7kclVm6jUyiXhJ85P7@xU_A~z}w zbH2bTX*y5yd60Q!>8U%%%6ib+PNVtB`Chk|gOzEfcEH*06{nrbwZOlOuWtg&Yq;^6 zP2Y8F346`SNbs4$iM0cY2LP;l@Y)zWAa5uHS+~t~4Q!<=x6qY(-GBc8xitrV2AvF> z5h>YDA~c&iwhdMVt<)6OjTFWlA~SZc{X*yBB6J3fP$<6wCV-sI+dR)-@Q?>BK0MyG z|JL`v<7F?q?hn7~dw=Q2FMH~zf6jM*>ydx*DrtLlwXPnvjvw~~b(BY^j|9-_bd6ZO z?(C2bHc5UmY0~}uq55T!(C=V2mDA|}kw9+0y6uvZ#l}PVbGL@UCvzYM)GCl9oO5u! zfS;N*7OjL3om4W4!GoJzYT6FYYqx1E!`w%rmFjXnN?&&bnCCLgLCyQiBOnmRF7*H~ zXz>#yo`6X!z>##N6wdy9MGBh$6N|58$y@fzQU+_eE?qOR+{np!?-B3@eCo&kDFEWw zU6{?f_4e6)xl^}b&i3i-Iaitm)f_iEbI2XL_o?<=@J6_7ywHIrhC;4BnvLir;qAIB zP;{8!oO{InsqhKuc|3PTL~2w#@F@;>D1@<5y;wG@O#zbmbS4;}OM^`a&RAxpD(dC6J?>XDKwK^nITu%hZ~u= zc@`qOBWT#yT&ExkI3c51x1W zGhpNIZUy)u3a-ehW>Y&mZs$ycZyuLe8oP)>xe+(Asb$WWjL_JM=LZ=Wq1hbiHMnpEd|rJ-C5(;GNmRVO_22;1B{t z9>I@)`T5w{3Fj0|-%Wrq-K7}ZCa8&-3BcCorXbVonqSO~-a9`O-XS0N7>FGNs;e2) zZ|K%LbIlN9oxyQ>n*d1$K8nf(h%y&4SDRc239d8pzeN>uT7CR(P!axg2=Je1k# zg-We|4TdqFAyL}KBpYjz>a0nQPCKhq)m44~z#RuNJpf?c-6@;*$hiek`C2xma$w|* zn)AZiHbiQW1QQ8B&Wz92H(RRl-Rz$Ql^`rDb&*r-{7Ikr55MJ$zv7qv;JH8dHmAVba;Qm}c zO;cx%h%&Wgx3sO1C_2KR5EOLqP249H2`$mcd}+|QGAYSTGEKo~MlV%8kp;!gfk#|? z;FmRV%@L2Cz0qB9$@d^&YuX?812G7W#kk#qCI?L#QK?gZMR;m7nSUER081w zv{VT1ErpZWPy;d9W4W?YTMr-hWYX?=&vkSQ@S_4K8`GMt@QA|>f~5Brqh7Rf1O%-g zh8g0{y7M7>R3z_B7r{$jjHh3`!0zsZb4~f#+6~yo;0Jzo)_F#p>NF=tH_R*l-kf1V zV=8;PY0upS6Y@3}O88TQZINpH(JFhn0Lhfthe-E{6!lwo=`YuSK)U=`n=#yq*h2${`fZ z8EH0}Zm=NK5)vX|CbQdp5F#R=R3@GY!y31LCP+5vwuT zZ=SnjR`fZ&U4|80?>&sHT}-;76yyW(r^&yR^y-@4iH^x&(Gd zQ?iVWe9;6cW!MQsVETpVavBnr9UuiU1Sv60V#^T-1VZs^g2YKmo)V${3IS4}6cFN{ zDJXug^!>_vNbf0G{-pPy_E)S_LZTp=_muve@#k|mkYkz%1Kod-h=oIljwp@1*h!Fy z^C=io^*4~2_LOuJtN}5~_oxzyI)O7X!ZO8eH3~L@)21fnLpX?ry3Iyj#<{?;;%E|@ z;20A@ECExdSx-|K4xc}~_^rS7>&y4w@Z8sZ|NnSaCiYGS?*RR{k}6pAB{fnOy_brAGDyb6A;IT{GD%$9QseRDU(A$Wm(NF~Fug zSDYfWJk-TOc5!z}a@N!b##cgJdSsJ>z}dKmB;b@d&yoU70=9E_@5zHJAry$CTMn1) zj_#^UzTHjy$*`JMaKY1MYu>g|A=A+*yWen5Pe>P0YdeixPD>yI9T#CW{bkVVw*U}3 zd*KcZ++!sc6r7~MzOJ4TBSB*Z$7YU%&f|dHy}wT^+>h-DJHYDX?H{7|w*l^9P#CS5 zr)>y(gLAIy)zwV$vfJq-V&Q_mG64Vi95xHFN!WheAxC_L!URZ8BNDz6ybmeNctqj$ukCFI z?g-mo^yv)zJWNv%hiiRPb{gR`EVa*T?nv@{EPbPcV(4Q6fn3e<%|2ub9O1C(NB_?z;F@zLBg~$w z&;Piu1@ZX`(=zS4W#}(s#IA15wP#@TzeKgEt^1OUF`M)AiX#UX5%07ObaS0)8BKXl zF=~z3tc%9pk}8#O{5zgTLKvXvDd+nAv0rF3>@QRD1v}Vrr_pftk(e@DB+KWi7LDc} zpskrzv^oWAG-<)(E>qrXxybD zNWBKP{Fw#AYQDPxMPP;iH1XmE;4+KKr0{6%XlhP17LhFmN{q!EQ`lg z(0tQ8Jy>CUx8{4CgN)BgAI2IwjeyeM5Rn`|g*+}HRtNe{j_HhK2QeCmc@0)r|EuwL zaK%^FvW}u*q}uy);9gr>9@q`o-JOQ#U-~0pctNR_=v-cXIZ&{yyQZM9;1J8CdyO8V z$NdWUpa5~h6VR{X@;BU=i<;8{AvY8){m3{exIl(~0<6|?;)A4DpKYz|Whp!;%cANe%w?ED3`;jnX- z<1x3liJcu`_=G1x`W%fRz&XzuSj1MtpZBRXPWp{mab>G1-dp!x+&D!!z|M|v43Po0 zo35n#y;gOJ$RPaayniZiRF3>y07{__CE#*O=6dLL4DWD`2od{EG4pGn1 zj9$<=>z^U=Jcte5ZszQlejY13vhhwM=dAlQG*QEp7jePr;0!m8rHjAs9*Rvo1WG3R zU8XB7KxJtr&1bg&bbZ$3D4M!hA|ir&k;Ad;C-Yv)j|g#}FI2EbDQD~A*z4ECGPH@! zaP}O^AxsbhNJl~b91ml=)U{lPv8gvMWtlYwVvKt=!bt~XMVt2~}yXWTmr*pwA z+?J@r+Sh(g#br5nXP>h1KBi5lGe<-Fe~eQ<67(S!E{{1}U9q>5=jUSM&w=spIN=jh zVj%1JpzbM*HK?rj*wne|P9&VE=;K@Qn8FC>xA6R@{XCwx^VU15yY8C>f89mG_QmJH z4L>Mo6G6*__N2*{Y|Gq?5AJX%n;L9c=77Z5+J`y2KOKucUBlA1MPz!;Xr-LA zHG`1!p(?}>r;=o;G;9-y8w8y&TT^tg3Mk3TeO6NIBt)CYW{U-v>0O9ilo8;_pb=Dn z^H2~8IpmZA!NbW3a#3jMCjuiBCItq11TQBjFeo}0k|Dsk2{o#w9tlE-h^zCEgY(P3 znerg<0RZb>dM%tuqRw4GE@EX+OtuiPIgmA*6PElT^zFYH(l#uthM8m8R;*E$^^feK zEEBz%eQ9jN(@g-4p>o?)+90K1-;+QG3gg551t-66d)qx@_eB9mcLP33UyIeP8iy1S zqj?G4mep0iT^Mhn?yt0w6-I*47+*H>D9ND1dz?9gmFt@Z@A`<_{3l}J@(ktL>C zG@c|ah(^=l)$ewIA*?{9P#Df7zNVWpW{C5Gtv-8}US7l$HWlI^aum0Z4A|KnaqQyr z7FS&G%RgZj^4>Z zci~>H+(Mo`IaE1g(+H~t^qD~(XjX|MqD9c6@P8_2lE0SF7YZwwpU#L2Ltk6BEV-9$PE z`W|Kcm(8+M-h=k)#w3}I>9$Ql++sli-&C?=3W z?HWL<;*<(Qh7cwUc({|EAGYvM=SbvzUW0nNv64qAfkZ=waso)2!SgT63akbP&(Ft6Z-+!4A7x~L$}25duoaw z<)s3pWoXE*R&H#Ek$B4Z>|777h=>`vIJ{CQbMD z6{n309BzHx?P-vB;IM=HFMxFtz+nUA@{R{1EkMx0z3dJYQx=PJkybg_Rog(Br>;*O z0c@Crx*#o9G@ffX#4_KTGVz)fY@3P>aq`;m5!_fue$f4c6tEzB8XU-; zma#am(@Vi3U<~Kb#Qo4d5PXv?P4)cH{(4sATWncUwl-}}5l7*|6YHP<)WmNh{PpDA z2f~O)txLC?;~bsjH*1*l^`Ag_iH-Rr99CMn&EZK7kK9^42fvK#&jAbTrgu01Q*eua z?iPGR7_k(cr*n+fK-MLVhV6&mDZ#LBGh#)fMq??(2_S^i)D%afoY7tB-vr=mHA3ZF zXS+`pqC{A;)#duVvBRi9BLe|PuQ4p0_x~85b;-Aeqr1Nn2b+8My}D+5+hJ#C#Pgs0 zL^?hPQPI{e+%lp5y)xyB05Ndz3peUh!uIQbJKc?sSkLH#{<{AG8YP8k5vrbQK2WRYfto}U$R-hL zP1)O~hV+R=0j(KbG`=n@r+naXeVGrBY#D@;il%x9TPq_fVDmSlUexv-C}w2^nos zQ)U4nZj^K}#vp#s`zBHI7our#pntOqyba=lX#EwNUR3{Qa^mD!d0l z!ikN8azZeg?0^1)A$<+TNpw-nq7cgS$qGPpp#?TMv}H%pfV!m?DyxHl<1N_5gx%8w zOgOXyR^_W{0%kh!jAk7KnKbAb9F)@=oQ89mLMZRD!z#)!=yciUCZ?X%6W6vTwT$6> zPJ>6Se-FtYZ+~38Gfd%~@bH2OW+%Ho1l^v+tadZLIAv&P!jDvZd^81xpnrqyBM(hJ zOlPldJPwa%T)V=vpL!95Z`b~Ef&-l^yN#)@)flAb=B_bvM-#F{vJErsCV=tv-u20w znA{&?n#Q>H%@myGoS{iU3^CSDrY?g~NHA0!jRfSGJig^Lh2RGNqQ&zs`)us&jX1t_ z-_`~KfomL|`Bj_Fo&OVX{t=o|@?h$r>{jkbILG3F6%=a1Wz(>m!DX-x1F1F&WmEEN z)_%Y?9G>%v4^7MIujzeqb7R{l)r@zuF}1*=1f!E?>A`b~Wi*KAS7QqvTSr|NDS7UG zwPnn``Rf4y>t27YoJx;s;%#VX##LgPW8lp#5n~gS(@mr{+PqYo@$2jZR(*`xGz*%@ z6j`%jTeD}M8Ed+hm@iDvRVCiA{whl9g;Tcsuehvj;kVxunr%=)SK+88)DAS%lc!cHmL^eok6OIE}F{PPXNzBIp?RbZQY zv3>)S_GM>)mBvp8#{<4CIFVG1cs5PL3IU2NMSapY*$K+FDp8L zx$fyS3|RrveY2pCCpmV|p9bh1NXdMrhw0{g%4WqiUb*bqZq|1@mnZp*$xq91q91+$ zS3dP3?Cg!$J`3mf7CbK78#kW+w9kMG|92RtQ1sK~JTiUDBx+Kcepi6rrv24OakH^0 zJG|0|Fnuui@P`0^>vq>Xd)^g4LYRa&nt_nZ;2>+tZ;xYlrR^;BB~1MmpEl$pF81#J zb!h=trVH9N+w?uGoso@IZvpw5L`LTT?}!PLfXDkod|vcgIdb+k=l>q%0A-`lNg&dR9{dwG98$N+4!%!XTE_}19z1GoL(X*Gvw$= zjSa({eLbfDy#JsKI2;JM;k>T~_*qg|Mn4skvfb3ThG8Do499xQ4V&g}I=T z&*}|p>&&3oY#Uub1vxnfLB+J$TNxqx!QnhuadhXd&fVXiTv^vhSzt0l39&aujNV82 zaP7eM-cUM^>4M;K;%0x^1;4%Ru(LOM=ikC%08*YK%|2-Z1vQ(*s@Mp}eNv;Mu_vm=+j%bu{*G#b>E=^!g7`NAHdGC0oAhxQ#6d1UrwQcBAT!Yp5KX;6IufLU zCQW!$p#BTCpZu{Y1^T{GSE(jm505(eZ#m$rv>cc4Zb;r&ZIrcYP08eL;g|sIRAkC@ zKYc1BX${Ns#hd)v;Z{m$_5=PXN&?&TXWJ*4B<^&r*#Pxzq;eW^#m$mhjU(y4VDWih z^4Zwk4aIx#T>i>eVt31#XYk(o{tp0H_tC@D5h?_&l?^1MH31dusiQJUZS&DnB4b$d zXY){L9s;Ujn1!YuE+Cg>ORXV=F&VQrsVhBmotPhbvu~JRA9a51cT&`bx~-Yshb8Eu zG6!aIzFZ6xhMdf_KO!DdKnSK`qme+-qWWm!s^izWvZ*S8Ts7_+)+ZDyfJ8aaidV!e z1hF5hHsUhN5Qx7oq`mB8ae!XKm?Y^JfkCWxtGtf0?L%7{+NSX$5LfN#bj#S!KVHVMe6sY6)sZ zJ)qZ~TeKUQi5HHvl>yWA*9!PK2*mE)^w$gNO*qU|AwwSXstMqzEP&bo74SC8v{;#x z=^DguaO~KBU_AAjuVNePZkyS1iJW{`jw?8RmpgXJ zw?y#r$bE1BaCMZUEUviZ+u`(GT8>Kq3rc|VRAFq?u$nr2sq6w8K&#oWl>)v~y~NJ< zs2Ure!K2w2kk(Q++2b|8nAl`;=6rXqHj(C(bQUmm5FT@0H~=M0GE@G-htuibLh(VA zOj1b#=@dGTI270?xTp15P+Bw+it%c6 z;>RxiP7s%Ce_X-Av4mnEvHF9t%(*j6q_eu{e%aiNLgBol?a;Z`VH`)qY;iPf?5w&d z;b-l2EdfBeBvC$@(m+^r3JCD-LF4}TJphz}vvc|)gx#$;=#EDIaUFhyzwUn?SjQmj z062f%kE@IMZH-&)*IK(#^F@_vW6m4Zf-0G5H;cX*n$x@&wH`c{{;BouJXLAPnenfT zj<2^>Hyys{gC1YD6>!}R#Pt`R{(5Yk;Ro7HlRHAjLxR{o^2yGR zPlF$h0(dH@^EFPRLRgaY)*HllMX~+tYhnu2c#T)4p8602b;+wWR!R!N4c==Ue-1l) z@52+%!2iOfT0I~IFSFUVXv`wIIPbL{a_Wj#B~3Qv?|UjVrMHxttTt`co%i zFotz%J)P`!r24rzU^VVzbK|lFKJnkLF5mfXJaNlodsp{*D&f|a2aX5q5dO*s|6Cxx z1XDOgVv*9Y%-w2CJ{QkXcQ&`L@l z5hg8}3uTlc!(2z-4=@;mR+$1TOrHRuhO2odKOIz9M!S zSd5MfyVtGp0ZLaVWOtdy0L-TT)pb(SC+{MduiBP(UGWiY7X1j6Mq z)j5PP33{mC7+x~{#F4MU(cKT;=X=vju3h;TT=JEq{|SXL6vsmIIe@wJui1{#$i|Fx zweA_Reqie|AlCP&zzzW7Hz9p%JC#6V4DV3vpqi{Eu+D8HW0hc^58KarE;MP$h5O>+ zFn%9bUHZ?*%dUAN0OI(T!`5y%LxX){Ys+B^z|q|aFTQv&T>0Q13FGu!P1$jRu2f!< zF?LkJbc?97FLQceTO}7OUKp2MLrn>HI{&e)#D2SF_14At{%-_*GN4I?&j+@0OzRyMO>RIn0{B19V$@O_!+PQM-4rB4aGl&r^24MTJwF;Lsaan zrA^{jGo?|YkhCTl=niVD6JcKoc5WBHnhBiYE(PE)6#UxhK!Z~6w>40xr zHmY4{szk7vsHASSn%2TZ=nCaRSQGr>c}ocY;+>~}ciuH1>dcQIhadf-Pwc6D|2fxs2`lE1L z_+@NwI~?7)`wM0@c`iKV4BFbe%{RPSRVE|2X3_VMv~9-jqgMhmrVrlsI{%0Vy$zn9 ziV!AAuCP96_n9_nfuxCe&e}nn0@)_@h#Yikxv!1k8UBh(Z<&tn{#yXJ7YnFZ4DIZW zIQEH${8dN(J&Av=)pQcnf{Nj8<-H@SOd@lsogP^a_fnHjMyJ&PUI=s;D7}B$U%m_g zaLuIwm+qaOWj4!JVdM2&>;qEY=Q5~Sf?Tw2`9ucX9fvsyTU#DGySM0yr@g|_eHN%t zyg6J%KPoHSu2 zV2wX$=8os|gdBXEuF{3!wSDL{=|`Q#}DmA5bN09 zbgv!3TU$N>DJ%c)j(izL{uhnY`GQs?ebL;@gfSE)+nN9v(awkFpho$~?H(<|=v;p; z#ad|M^jaU>Uz7R_l__{;h^qj}1yEr!iXrP@);=d8mFjInQ$XiH4O&f0=lJO)erI^$ zr7s-a@+COBdu!R-pSTqVK^N^Rt(A5$Y=gjdW%3=}ov^z*VHa4u@Un|_qmBXLA8OJm zI5|i@$TjPRO+nd*gC~24JV3B^EnUyde#n;%@!TB@>tY@B0lna9ufYPJqE!e4T`_{QB^unl*w4-)UnGjV)3fP16A>awo^ z@RJzBr@Kw}d7Cfzl7D^j@>jkm1HKOcSO@C04Ah~_Y)ucoB~-)!TDsJcH|c@x;6bVe z#^|V}=)jB?u+SPy$qr)f!ID{$Ep0MGL9uhA)=Xo&!onfS9pGEtLdQb`rsV?+EC7q; zrdD)ehWJP`36taum{q%?(o~q6XwiGi`h#6-9bH6rH=OCGY_pqdn9vv&(1KF7e=GFZ zc4ew#kHxjdlyV|d8OMLFaDewar>qc?Nu+Psh%66C^+O8iBdJ3!s+mX^VjznR#1sJF zWBip*`+AJ-HVos4un=5_?gbyI^@DkT0I(F{#0iKDqX2r)d4^5Fou`o(P7IHBj*oJF zc(lgwslxbJE!@BWXdDG%pV5xjF^b8GFeG4o$|=hNT@Jd+y1vo2d!MRQ1D3* zA)KZUyLZ~jtu2qA+WjD|c-rrAaG!>$il4f$_ZA$a?OJS@M<4!y+Wf_BY0&<-#K!Q$ z{`pV)*y-lg58>$U4cOZ9xacCCF_>|D%g0IW-nhB_)F(_E?q}fK*Xq=2AJhf}$Q7MH zyH8==)zz#?vR|j<0hUt40~U)Rtinri#T(y-&w|I+=|9K1Dflc2(Vl^_`x~2cKQ}2^ zolthv;m-Rm5B)EI-z~z&4@48_HfR0B#msIn(t^bfU7}xp7CpRzR)9X<4eq!3{HOh^ z>3OgEE#O*2N#R~N;{tjc4mjfCwD#Jt@xn{KAq4(=P2sT$VHu;#iN=I8Zy;O`B+K}n zHIo(ElwPd|qN6X(nKi!SNor%W#GjY~t4Z{#5%y^57`35{Y4E2}zL4;E@GTzOaJU}C zMfapp-)>Nd*`Gi0dg8hph$mhsaCgdV*TwUn^29JK{;ei`D}tt&_34=G1z2hU!P?Td z^NkKvq}Jt^s4{iq*918ZklsD{Q`Z5A?4P6QwjTp?Xof?@UP?N}nh0i_yE}!T-}Z3-NSeA_ z1g-(^UDvBrKue8jlxACd0{w>&#(f9(HBSDw{R^M=J*#K${RwvLxpIcj$M&`Zu5lTx zUA*vV-w{Us6sGAR0#*QrVxfT6u0zcwbS>4BY6eBtJfA71nRdt`lT^(++?!(1$FpB^ zlY7C_eml@V5o1^a9P)EyQ(0*&XC(VLruXNJk0aW95gICn0Af(k5&;k8!vBO1n_If_ z$ghOpUWVsh{r1_EwVg60P|NH;+eaSZH~c?M)bGG4{FaiK_HLa~3iKC!wMe|)|n-QN_tQH6F24*QqP_zUp#2vQu+LzFjstw;+^iDeI9DaFQDp(x3m$ zG>_#ptcR=mjW$JBWFx0x)K{VFef^JLov~^Vv;c9HBsvaB;%cT9s6l1YNr`aUIBC28`$#W*#Rt?)bsD zD9Q-KxcD9C$9IeRxEMCfmS*11*^ROQCJ?8Vi$=1UtC1=h0EvY{*eCFXqJQOZ)scTU z;`W!|=F>h}FMU16@J1K#`Z1jRYn<4>70*~K@t4;M-~PA( zpZKT^V7LGq?h#J@NhF>O@lv|*DS=pMRAA5w;0DYdN;@;)f7cGvKP+w!H}gr(^yazu@b&@Rw+sRs?)Q8u96T?#L;cld<9&vO`>#QW7Dr z<+yZ*H@+25cu1JGk9;q7c2D5x7ccPQYsdS9{GWjJ;yz_;vHgKYn_c-ZvG~#AfU2M4B?7Q5njzv%#{SJGLcjoP;Wf$;^1_i?nvqW~E-F zEpF;qpa7oDkkV!fDvcFT4`|izS2guaI4#T1V+LOi_)Gl>mZ|U7pGD^1`vD|vJa;O7 ztPP%XpcJqQNvT|=ZoT}pY(J6a-7ww?$myUMAfOOkV4St|cImVy2M&fPP<{WKwP^ZK zd+*s#6N%pUs!97&WqML*y+ur2NKb$oxv|*5Qol33`}P;4Y15fEZxam5h3FP@ORTd4 z15@mwQ(rgo?81qW^FEnC#RA)5dfJ}^T>UaG+&3tMCE*6yCX4Ct2!&uaYhx@b z?VM}9vFx!Cz5F~@(-MyVfF8dwTzToQVch?Par^bx0?GGa#{gADK2Vy7bdc=@;K<;g zudi_Av*dM&RsjlH(s2-ssXVa8Lq!FIW8*pLnyTN`*?v7Y<2#tR;TNR8W_cC)D*BeKG}H8RBx{MZ;l-!h2p@1e6HVqj&6DE?yme*U;4|? z^rd8ESA~i)QajBO43NHdEA&1-=uRU8#2$Bo7J_)UE+t~l~b<2d~qj_$srWiPzLSB?G%fz&FMiDMpp9>m)A!Ng6SSH}Q6ouj3aR4pSPOhZTQDiObw4(z zTO-A(9znTQ!R+)rV>S#i_-2AWo9(Nz?$p_#mJOVUJ2hLsCPSfk*l^;+=eg~tejq{% zPD@X}z;eJcIQUIrT507M6EKDXbzpMkmlqe)Kp?o)G!WA$Y`Cz%6yT|qIKcCv?v%d} zZu~?I!)H3-a{+uR95%u-1WZ^0@^qf+r!?IYD{&okQOR+gKW0-0yTS#GH+c$T;L8z> zjNR!p^sb^FWo1_Yw?CZc;C*?ngD98FUF%B}4|#fb?i#Z(OxX0n!gGxgK78+t2;m&)fou~bd+w{-S{y35FGfzUab!6#SdNyL?0n~m%1u!#~ z04@;1Qow__;jbFP`QL)8j{MRX_IGh~_dVrIB7pw^kKL_0oZPxdIJOht|4urWw&3xC zFaA95{I^p0c0mu-YFfcDGSWl!sq#eJ?m6ki#rKjoo~a!;vRD+0^%Kn zBxn0`yx?j7i{AYUjnfjs&_0hSEU!8vB5XjIde0{Nuf^MuZ5`!EAq-R)u?zz7Xm0qg z=VJJJX}aA_hu@EfpZ^{_Qa9uAmkQ8f2oEFpNFhAP(LEFcq16PfLLj*kGzuXo%BX8U zA6z!rTi+iz8>HAn>D~ch0wk~f)xO(Y{Qvx>d&2aBBR_!4_uhn;;GQ!N?d$lK$8|R_ z16R1}$sgl+-ZO*nOh^w8%Q4!E2tHki4FoL(JjgBZEAB;K^0@IwcCW(G`^Yo(0RZdn zjy!w=o!TNhgGfOkZPaf5Ff~kAVisDHS;>tAOwF{mpyzB0WwqYfsX$}dTG>CCYOU8P zw2Yvc9EAf^*+F>;Rz z3br5Hw_aa$AC6#F}uyJDOJMGY@Pxbf>MARCAx`;+kO3M20szJ`W;6>*kA2~?r~W7D?IfK zUiGw>hAF%v?1w+bUw-&+uyg%#EwHeCWB{g5z(SwxF?=P!uaM*8IR#)9mPAY=kaMj; z646{|!X^2Ij31VaA$aG7Wx;2IK9@$WR6g#^QwTSPU?vl@Yiyx1S=n(g`-n6C@@)59 z35@JqT?ps@_q54nE;jH7G|49tgs|_A&S;a&SLA=+HBD)n&guh2=|e`>8i$aqatY6jRxocFoH9{?C3W z9{!+D8#eJIhT*Ff^fV9;!Ey{hSUQ&^gD_4mjuju5D_f;4#9D9IdUt4QeX1KztAea# z_K+ov?&nVH)d_rw5BO&i7BQ<=YzEC{gDH&M;-6`IPBtpjrYM-S&ohSp@Y)a-ONTCp^TV&0oDB~ zQQ;W?V9;uHirznU@cyOYs>}X;_~0$Sh@-n7jA!k(ixTj=#6FBabvq3l^XFk2G;Lq} zh+*SF-w5)1ye=U{mwSuJsDA#(^?5r3?k$G_XkA9Ma0W zh<1&)MIJGC;!YkFu%iW{kD{Fi{`A>b2lnNRk&2OBHSfGLA#a+Pz$zJ`NgC-sdy#TB z*IU((39vKP(u&I(xmZa`@m%SqIkZw1HXooV8jLaNnwZL(fZ2QR8B9Jlz|9GWi3%N1ug+LQuXFqkL}PtM|RURRsUHTHSO+B zkT`wcr@Vp}KKg1b+?Qh&Mi9$ch zj^x+T9lF&o@GjxV``rt|hXkI0Fj3eHm@cGq9}D5*3IFgAi`NyWX$9v7mgqItNQ`^- zP+4v{61-1pZ5~Yf$y4zdG;!e<5?(Vs;>7P_d)wjI&XlI~YSwtiAg&%pgr+|1FC`!u zdzm^|Qb|LT*mzGy{3?CE@lNYIr78O@yzQ&{g*1&^hCt#maQ-_<|DB6Ni}!}ledOQhv!41sI=&yC`v`Oh;k-aRl+=YD z!{fsgK0zM9Y6M0sK{+@#uo;J|VSqWb>AM-LRKH&p41=wYQ4U1InrZudo_P0P6NQBW z8s|Gh4U~CHG$x8PtTL(#rMBsXdD|i}O%Icd9U~>q&QL=W4Mx4FHcY6p@L9IlEZw{BTWU zTmd~AP{q}?SpMQ|ckw&|r(=A*_j&^d_r;pR2+`Z0Whbmf4sO!-=3y^t+a|I>g+zjm z*`IvC6jm6g0Oy{7h5LRV{J-LZAN;preA>IYGu;5k4})AGa09?0k&onpALm9q9^!+e zwZk-N3`-IYbVEO`6PS__o2^8iS0`H*U}4y~y8>YCPT1adxP0f0 zdco6vha3K%YBjAWcs0CWbDpwKptp0bWSt-YA)PYTBaw56lT|N30KWV-gjEW-h~dBK z;G;{8t}j(KLK01-+?ue^)4@v%?5YX+5&%^eiC;5%V*FgHma=Zv3 zKx5b^;o<1vS@;ze8(`$DO1JOtb93J$Ui`@Jtse;qh4)_oi<>UP)U%jZ zwg_=7n5=}R`U0_=Hh`G1acomT&W9jsq<YBA0yn;z~C+8O$Kl+(wTxx*RX+R9nfJOpR@+3bPj?BC;%%h74tm~OMD^*_rxN* zD~VGB5CXy!CQ-|@5 zeT1-;fA6Z+xv;x1UlJ|-Ca00)3kRC(|`3VJ`)ZXrXz+n z=%r;f(2CC!1D750XT(S{>{~PFDgeYlJHH5LQ1s)7)7BM8282h8d<+*u1@nf)f(a?q zVw@ysiIqUeiS+sB2fDe6$*@Q)yNzimz%CfBog>hsPWFb-DmpVZ|LqK)+WjeDFjLE5 zac#~Mbx{3in9tv^ZhqJ`Y;hVDvR@{PcbVC^S%O#!wng*o6nH(GAv=rkqF7!eD;5t2 zN>P-~78`6BB(p?rb?SjY)EO5GsiQvErZ%$X(hXcYbc;I}anL&~}Muvm|kvOhbRh$L#Kw!_Mw0 zJ^#qhI_JI^t3VkK-KNx2KN=vwHJ(5X3xHTZTp~ypJ8HoRatPB3C&v&EDt#&)Kb0Gc zRPTl|h+qJ)nh>N(qn2sg@TBv3?^aW%B;4Hhcwrl0B%^X^dN@_kno2egFzX$ytw{Zz z;jWo%#AieP(BdlXk6#_6O^BjPrsDa%hRm=|UXoH9B3(`Fx&a-mEV4-{oDn-@eeUD~ z72ikWHOLvsgH17@neTfB-Q@B-paniH%S6V2gJww3jitkeRY z`VT(n>j^$aU;ri;l@#o*2P5k?tecx?_FRyaD8o9O7~m#psZ*r;OnUyTKkv{F;L1zi zs7bH%&j0ahb>arxKHQ9FyzUfUGE2%Duzm3cHiidbx%e2rIP`dEbqN;!i!g>K!}Bo4 z04-PhBpqFnqjvTV7K(`N2g#s8`^!^W4BzI)jYoy;FZ&nRdHH+q?;zIw7r@$>$XLlX zHR^0dcLwb+w8i$2XpQG)+k-P64_%YlJpY=fN5io1l!ZUbTtk@2y{+vF$|c-9}Lgk`{jG}zzdqPRlACK%Qf+mY1%G| zhTEpD*qKjTJ5io16R!KRx8Y8}@hv|c-F@Y7#h3m%H#Yvc_Qw^Rd~Ig)^$)NHsB@V( zOvr-Sle%ZVtl!LxkI0B;b!i^4R0vrDVdEKNC*Vl(2_@P_zjHVcc#BP`M+@-9!Gb>X?wL4;t)p3dBrsY3C>0QF67He z_hv8hx_&xsWCp0|3BMxml!L`Y$A9kb-{FOqy_mzWt&__Y9DF?AOX6p|+b&(AU}o(l zA;HLulCMmYh?G+ki~KuXY44N%4W#2Db!-6>rAZ-fjDgwVFUj>%R(rbfsDUxjJk$7h z8aP2law3!5M^po1&Zv@oT@~ut)Ob#%?-i2KxIe>GxlJDkqn);jU3AthJxC%Y`C!Y2 zZYS&D5|*tMTTdazN`*Y`MWee zb=Bu;z83qlq|nxa7Jk7|FCTyOn&?-@)3C3Ol^F;jpFX>J7Hz8Yy5!sh^b7XBvtpNA1Sr|75=w=n%Dp$COhC-{kfDc~8D+}{FAJ7gdoC;=eAk|fxR)|~-+O+O zk9_QFxbRQKIF69>c1lOH7VELmv9WBe(9Gaf@?Xf7y_Y}%#z6&qgqea6G$JraL68sx zCg4^HmyPF=gD-%$S_%E!hrVSURw<_$A~XryC`=l_9FwY3_PJ{mxiaz1Z7QeGc}OInh)vQZeAEe9 z>2_E$r+>AGl}l z{4KAzb0<4s26&oB^Ow(PGPb53cnFvw(*>&{L7O&dvCzgFJ9m!*B;>5`8%GC+&^CzTu^=3 zQ48coz?x}{E~YiGVyH>~&*F)JCS5Q+l!VzdD+5bKs+BfC0C-}ZX~L`jztU9NNNkyB-q5cbMnm<+ z%Tq#;jkK}&Fre6(v3z~%Ps^`vUQ~A*)-{d1HtlD~fmJU3cLh0!!!U&X@mb5~T=P>n zzU2+`>K?ojiZp}(xKJLB#B7t<>k7~dnS;4@aP{7qYh*l+(q~Qp*7ki5%3c@ck>iLL zYSazz^vcMSssvD#dR_tPT0LdTMzhXo;l?~gmyreKN|7X7Hh3aax#}T#5d)kX;M^eZ zyqt?)>li1f{V<5_CY6&0OQS{m4Ao!JN<*NN=qTr%=&3R(4-h2Jpc|>xb7KLu)ow^L zMu9C_%-NlvZbt(=)UZ&xI9-(s5rhtwVN(oq8>J@#S;9~5>~%agiqom#7qpz-4$na$ zEl1X*sUl4%*d)tdMbac18(t;Z-1oBXnd^_VP8^y2s^Y!$gmVrK13VXS8bmGt40Kos zh$c&jelc={yd%A@r1zzI+}W_J*J$(nbj@hGT!Cl zlVe%zm1R)RZML4J>fIt#JaCkM<)z{sk=YccW?+%q#=VWsk^^V0eHrYM48|a6UI?$5W5jMh}kQOE&<@_4Z0M3LIUT|#Qh|0FQVp9bj9L2$@ zter-(jVR;Mha*=d&%i(9=wm(hN6@Vs@h~o`YOIqJv!(Qh6 z)M?lqrf5wdUA>9+nX8po+>GU%lj9(VMLdt-A$b5p`lo~0HQ)dYjvQR7jsq0pKHl5C zmLW?9emh2U86>b?P9m?zoc!3DE95=TWb7hD@c*v8j_w9*!|9ggk7Eqi!f{g}&PK|R z!|WSb125BK_B2T3KQ9t^W#%Yh&K){NbkOt`s^h`gJ<1A@QnQ-1P>{v+i?UHxN-d9O zOz{Y?XO?RACpClpa{kqtG!C26y6oZsLw^1(18Y5qC2ViI@xt5x6C8h!4u{j!p^l>S zg|{qcMEeTFtZ*sq{cO_5Q*Gs9abeua^0VYb3?fD@r+tjmK96xGAk z#pTi1sHir)#?3H3;Of%o%)?+o1Qg~34Cn?UafAW8&^FIjiD;`NQ&i@GYe>ypeMb`% zpb72)8Ww=%-`~no{O4lGHp*;){+1Qj4gx?nlOA>Mn&d!iTpFbo>UXN}Lh=0(ZtC*WQ&N866~F?iFeM9O2i?vn{02cVh83$By0i4bIL^fVLfWUh!9)aFyE_9Hpr#6`d?--9#u*jON~S9Y9mT zM@Xshr;VtrH#36*@i8HNx zQ&`=#y^pC;-1&nWBtAcaKqt(RbGoX%I!?v-ohGI#eiI5k5j z&5c6aW-af@RX@ZK8ohFEh8tm&cxOoR;TpJL+mB#FU(x9?=T!se(!N zvJ^H-%Ud$?#Jb*SJY33}GS6fM9y<9{!Keu-OkDUuVY+5||GQp>ZMgKz-UWsdjJb5$ z;|hN#-wX2~tFne`BM!i)(~?Y2Z1Vn*xR}lzYy8m3MS6guGaV4YS=zpUT`EC-ZbOBO z?DN%ZgZbH?1Z><5pf39`?Y#PBAb!;!-WU)tMOh~l_e+>z5$!Cn*^~NMPs$q1N48%K z)TnkG4Xci7aTpSGjxGVUkqex-p>cgvE^pX0ta^53stmEfGRTU#O01()dqA3DpIw9X zcBwI4i-V7#a{lpdu|tLHLHzjZZkr_wRY0afsdTZ)Wa{bcU?~G{u=kE8 z1+Zr-&hBx~WmGGb%m-v>eH&~4tfG|f9(2JYfj?@COk>>Mpw$ii%ACRFlcXU_*^C~9f<*%zM9vGi@+I^A-(-i<@VXk^it!K zi7_@{3i~01hhlKQKI|O%ZXAuCdG|LE>;4O1VHrS%RCkmHuxtW`$SRT1slbRXy5h>3 zzFUKB=V}v~511BRXlo<8Y0G#D^ock$Xnrj+Xs}%Ku)tVVqVF-{NUTy^E%Sr~5iq2{ z@!gMZ0BhsX4-!J!{`zsRJ+5UYfDMf_PfJVzAA;RkX9zg$l*=xiGLbOTRIwz_)bp`6 z=uJJ-3gS%*gvNc4{l{w#td{F#Jy8ex11uo~m4U&IowBC6Y?#v}bRic+%Amm--;mjX zs^nRs%J`XJ$MWn%!Ex^?*6%7QldUb68is(Rl=`G5!6mM@)YQsON?Advd4#!J4@ zE}Og8m9EIrn+0V z-ene8F;#5^GrAo1v-R2$wzmBEfp`5sT8_Wz4i6g?M$C#w=Ceg`lpF*X5;><(teS$v zPQ__GJ)9Z9SQF?BP`gMTw8@oatB^W5^7`A%P;}3&r%T7+Xs`lc1|TqKbeqEl!}OAH z>^1)nj&FJF6nzfuOsujg*_pSXw@$I3 zLGQ$)na>{W^t%&z<w#q@S11E z8#DtKGMldfd{p`wWQu?qc1>h8!a3IcNKI2*$&&4WqaB^M?7Mcbn#vB_+p-&|22qA} zW}DlFjZwlwXt5IW^BpM3dD2O@g_WL*pMBf@o!*CI`M7S?4OXBSFV_D8oqUDexX zhqRh53IfwP>t{|4A(K;#^?7NQ_39b+=aX|1nt-LFes z#AIvid;--oFq8co+dv!p%ydfSa>>~BgZY`QvV54I-;)^(02~tC%N0rR-D9HfV#4t) zkL_2zUeI&6F)SdBfW#@NFcH)uoZD#RX)0cC z=hB{*ut_C*C>cM!T6?vgf!PV3<=AS}Dn&VK*rW|wUTME`v_go15&&?`T`9#7C7eI< z>ObZ}Kj9X`0^$TfZVGZiHSh}U(7Ps6wzYWQ>&tpuCnj`Z(nfM9OQ6c@x{4wu?v!kn1UgiOo1LzhEnlO$F4el5H^Dn&uImmhY z{(}qMe*vuh!;=H2LhW>2&UdbF0Z^8H-6_CO#dC&zVEcYdU7^~WgRO`H zXv$0H42#E(uGYx~D>GnTKzEI$*(KFX=2|U&GcmDey3(#`fzDbsR=Xw5qmY5%G#+H* z|KQs3n>7bpV+zTBI*rZ@HZ>&HDTQ-%+8R`r0^6A{hzEJ@5Nn?rKnBwgm!b;BsGB7< z4P?Sz*aQNsc}r*!4Av=Ft|pzD#z##fo4#{tur<1cU--d)(2dhKuYUCE|A_4)19oxm zgI5uWSP=ASKeZTr4YvqYRNIsfYCYl_Qliwxu%GR)U_!WPSNPet?Qfnq`LDDLZ^qyj zn8MU2f67qni*$yV!4&1UQhNq;i-9u9I_j8}mIV?FE0iqcD;w8GJIz+-Ay+;?s6}U& ztXcOOmzF0XV$_D;2qC*;X! zrtH`43P*Rt#tPquDg3n@7D5<yhj}@E8Ckh%SYz#ZTgE+VJ?YwnX&4r?HL_=^AFKB&8!Wz`tI0f5nVnf|k=mm# z0mLLjlGU*zG&+-bjhcca&G<6C)&Ar*7aoCE;ELD07F$~$J9kQjXh&wwHEfT5As5F# z8d)?t#f16|&*U+A7rh1(!}qMdhLxvV>(!9RoyjNF8CkQv1|c2QcJFN{H*6j7@9ueY z>=^Mwdmo%uA{{pkVa82}dES9g|04~%qhBm(!;Me|s~=Ln9!(vb|IC?Z z16={!t(W2*8B9&wcmtaObY>8i@uRt1-sAD%9i7iv`jSmXYYPH0|UGAU%w4VVJ=%@P!6YKs5VCBoYxtt>Tw%*+SYu0RM zsQxx;H^>A|g54;&&r{VJs%4q5*`9(%7Gz4NqE}`yyUWRgYHT&pIJiprEFzSZ(gA(rQ4Q3|T6hV)Rjl#3>8jGqp0T}> z*v8FM)r7dxg*!PLOr&??h+*V4=$HrV9ws(8YATcuu!;NN>m*98RA~o1eFlcC zNGl=MY$D&L`t_VIo3PU8-Kv*fDpVV9A+*Ihi*c#}wS%Zh#FTBDThE-P$c);dQs5O? zMmBe`4Ouzy5ER15jm3rw?oW05>T8$J+xuV1^J=`;!K>@DEY&#$x*4;iu{UbLL}K2= z6xmuZsTgg@M*PBK$IjlG-a)|DmcQ+VZ+t&b=-UYYetPGlu3NS6ZdR%B=U8)gRfEnJ z(31`AFuqt8FlPfv7!zk!&%V|tCxNkfIi=rMWjR!JHT<`Whcen5Cy$>P$YvOwD45d5(D`R6= zx$`y_T2B9CeCs2B0$W=iySsOJEZ~&MNoS@=$9pfElJy{CrBwtGdpCu;X?3odaKkeNFR|o;y zz|P)@<^K5H0B@jk1DNh%y!L8{2eQ_5EKI!yH)qR|t)A75DAm*)RkDs^dzx(>n7$>G z44w153L3^B<~#`kYg2%GWA}zLK{^3GoLcV7`Dgm=OJ0m;f5D@YU+jSgvF^VNtQ*sF z#yOoY&V|?wCaZC?N`7hUSX+uQaNbBYsdH$2sv6hKQbEdi#C7ELGJ$K8$m)hnla|qf z77e}JG}^?PQHhwO-VZRa0N~nduLZ@Ro>AOTnhk{*Z3KG>_cpAkMv7*fUu9F?3Iiq> z89{AM8X^y>R{&f!^tKapS<$nqy-g|3SyL3P0eDO6qM0nQ#CXTgXST%yDmyJ8aQ1+0 z{Ak%eqd;c{Zlnvw|4gPahbe{{(aRbJU1OfeQnRJ|Ykjm*Z!wV=qtR<={iP^#R(GF* zVJ2B)c-sh+*M8vkF#ygv z=T2g&f3?5!(!afnERHn~KfR@!=Dj#%$;3LFz!@pR2DO-V)655gJEMDke9KSIef7&U z;c_k(8&V0LQ#)obja0R4fqFnbIr%CS#{eS4{vxwGIVpk{foq7|S|cYwW%3lT#y~0o zP0ApwZC?yMNrN}n`ij~0n#{M68+Ixg^O_CY87sHioMqwknboI*D_5Xwo2B6vS`dXZ zWH0Z+xjdPX0#AYGaFeBZLP=JcTtm7HCC>5 zk!B0)fwJZ~X}D!s6_k=fhYdIBPsVBaec0WdaMA8v0Z@ozX=W1?{rJ@b@)bCC#dw;H z*PB|0?$+SNn(+`km%%feV<^$UjWnvfe^r5q`i_#tJCmJPp4bmA0o;0~LzTS|$G3dA z{F-0Da{Og(4x0d0ENFMOvQqJ0Q$yU8>95fAP1%c(CRMrAK(>ue-l4Q|SF&Yuc&%vl~i9gNcU+g&i0>Y81GI9$3Q%PcR8(Y|Cb2-eK+{S z0AdPUQ1!|sTi{MC;Bxb9b1vPF`+B;{g~jV;+W^e)A%id3CcH_PY9!7%9S}7@CtecO zL7F+~T!q0J_EqTYcnUL-vv4*MsXi7Y_%i}|C-Jx7E~tf3z{auct~5t zQ+y0v6YOMVrhZwOZ}V^E>^<;&ZXH6R@>);PIjRFM##fLAlE6Gcp?UL90nO&1ii%3>IzJ zzO@i<4cFB$INCDZSR$Cs*HUQJ6!;Y}x0Rb(Fr@kBYU5;C6Do?#qc^b-i2Wt;zzg`Y zYXfL{V6c1EKHNnu}d&2y2Of6%=i0vF%|3S(?jA38zgO{-OFmhqfv5mxZ9rvr zAa?C@!3|lagG7WfGMr(^Q|VBuzC~<0P-5m^6SL-8oCE#MY+Aa&5GJ?b7o@ul%kV?v z>Y8uH3*Y#DY#$jC^Xk5R`MmA1wwtn{7HkRx(S{m?hT%~>AUeVbh|`-oosHMd-iV7Y zUQ9cC|0hi08FT{>NQ4mT^VuQ}&7isl>ot2r@ko;K7fHiBm1uJ{hW=#_D|Tpc1c<2u zA_efaU{vFi(fdGH4+*m`sR0237~nh=_NVWio_qDn?k*Km24>{%t>Y!5B4!|qlzhW-sbvp2LfV0D02Q~ohK=VYs|Yf25{vrO5FY?Hk&R>Oem_XEoc z5lB4f9=yqvh&OI-K9iGPfkTT!QW!Baj&IXL>(D&O&UyfjEpw$$nXoMP?9(I?^D&O* z44B+&g?bJ(&tL{k>@#Zg_=NQ@wwK!sE^~FC&cJ!a)XEa!bml{`9R!4Jhn+vX=wpu2AhO?e^Y& z^XTs#Pcgy`3-2d>`?T|#-^cd0JLCH70RZbRyl&igE{**(KS|6PXV6Wd1uDo!E@%!z zQ?U0Iqig26#e0=-nfIPs($j=Ol!cb2ZTVoYMar7!^de!a)6qg(H-mEfmq-AM1wN`@ z`MMtkvaMlgz>TggqnhYgn5kMx>PZQaJu^^-N$i?Mmp)Hj?{2meyBe-n>kk6w zd?8sAp=_@4C&+Lx5KG`L2N>EJu#{pR4Ku z08Nr$H8ySoW~^MV{F>4R7%GDcNovL^?W4H>50zb^%*G2MM%IaMTsJ0`en@BPUB-yhe9u*g(K_<8u54 zyzn*GpD|-H2OZD4;>t!a@N+t%tgQNU*jt@2GC+$krf+rjJ+Suo3o=^Rh#3i2r;(2A z#KOa?Y&dhYold{Q5FAb{?@8YlV>go4TxjbL7TF@4h|4(6EhZW`GeSWSA2|3NULeSJD9x5Q1CyN#dWw4;^K6G`V5gXe_rTkY)qYTV~{ z?iZ@9`!9fXYRrykEz8lcsN9-P)v_q)(iE^nWac}o8Ef*+3ghOACQ2@6$ufCgx9Ad@ z0(9PAcc{8qn05-Ai`x1gKqwFl%NrqC0P~aNfV0Oa1HdPjC3@@v2wbgs<4*J2Q=Fdi+j2B5j#+by3o(m3siJW zQwV9jwbAMJBKvLH#Uj9s=d5PgM0Hil+iTF;#<9iT?F~$+sZHN8`|xzwxQG6vQ6A*6 z^9MH!Bfs7S_vdb7*nl*$I!~^_m{p@6<79}{q5+NH?+oj)K{}l6DYF{1fBs4eYX^>H zxmrwnhDbMn@R6mT5oc4s_oM}&P5$7FgEJ>^hj8yq#dZ*|4Tpd8hEwCquK6yG`f0bZ z*dRD4rbKp}*V9mfJFnfcv$?qG<@%|d)3S`Rmif(wO~?7rLAA(*5a2Wo=Wh;j?x&pi z-!b?Bz?AMS(Evj85R7sUt?s}1ajmab^5z`w*}1$IL9FM#>iHVObGTS|k^_LT5ck#+ zRp#9}XOC#Dy<`ScNbKpxwqzYcR_X?rmhq*U%&PTsj(Vo+w6$Cb@sLKha0@^96>eL8 z?|AI$UyPwm2cLO${{^t}eL4J~`5tdPf7I!>^Nn+C=RB5!V?A_e3ITTi4PfJsMdQWM zXu)PvuGB`mCX}Ex!?n`D94l)bFW2OhQ#MN6yiEsuG$U4bwv*9Bc{gTfknu>F**6*% zOuz8V*_)`kF6WHH95S+`PnKTE*#?923Rl@9!WhN24@K+GR2qNi^fW-@oZ(Dl;c9h& zZ{0YnbFAZH;}T-$xifPd;k4ro70(x~H;qM`3|UxKozY^~C3Lb8G*kJ)QE>I2*8I5O zR%-~M>jNvwU!4R2hZhSs_)|DF{nB{b$xFv$*Ze$=U%$fEmQSF<+1)Mz1u_>{$_Kkk zbn@mPFTGpMk8hfK9}lbP8wkFi3%`L7RxR4u-(MEi zN5c@VnzI&c%G1M8^hPOCm#qFmEdf35gNafuS3LUW!C!_+qW~p)#fGsQm+}f5ZUN#0 z+7I6tp1b$UxcbO|m%L=Wm$&hD;k)+w(L|<0E$Z9uAMEm491s%y=}f~3peZs3BI98VB-MZ`vVC7;M4tWptO>bITke6awntzi3F#odU@D

N#I4Nnp1pUm?c3H4x=i`jt{uV!f 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

0>SeY^cr10)q+7=-%Ae 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%0DTpstE-F{!ZCvTvgi|`h4=arxT`-7*gjd$?< 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

KY^&?5B~NK@z?i%gOARyaKE3egND!9 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ĝp_6X4hKWke_8K` z_%8mUK3zbDIuTUP- zRoxzPAy=OgsZ=mE#}}vj`H2KXOL!P>Jq5VG{T&3o?&a~qj+X$yS9{c@paAd%p5s4g z|31Dsm9=IH6y`h2E?ml@p}H%ZfTPEnf>&fSYN^V&QAObz4Xbvx2u|pvB63$(1a=pe zxxrN7Touh#WF=c~0}h_Bx3vhbU?0@e!(c9ywW6|0Y$nb|sQk^U^b*h5`e$nmmbi>X z8PqCk7vw&@XKZr51r3i#L?+pv^}QhxQJ-@~0BIc)GraBMv2oMmyt^mzq9<}12ieL8 zxn0Is&V$|*>L5)#bG~~>eIB(ij1h_ky^Fyl;JcCD40>-SFfDP9Vn!|Mpi>jbaziur zR{`3s$2eLXEaE(r{-cp>hP-D%qE&8;tXCKb0$Jt%<5a#+d2`e0al! zNe6w}OS5uZ=2>^+&;RW##b)6fiz*cABfTh$sbwVrP0&K3JeQ!giCNakJcfC-yJz8d zLtz{vk}QQ0-z4H-Z%)}4m_d5XkutbUKv=|jZ z%P|r0@j9VXmgIa3s085FJ05O59mu*53Jsn98Cjmlht6hVoV1ZxN! zM}6>JIIf!Y!q#16FKBqJ1{W!{S0Xp|rqWob9!2}I-#c+>04<;W~H~v9p0OwjT7LeDb#xZQo`nM*VE^_Sh)4G${%%vfu@&^reEmdN2zv z;r~s7Lj@FItYyM$)5KqDvly@3&j{0+cnG6(X zfMke5@6EDl>nyBuiFMWZ5*wJ;`mL(TAOpCdt$U$%SM}VLxP}*LP2VO z6*Vr?XRRk*%3g})Y`8tVVn&(*_xmI6UwjT}4R5~jI==heAK=IDy@&Tdd>=pi_-FX> zkA94Ae)F4n_0?xkowz?f;_>kjzx>_rFplAM&l`Ft+abY_31Ick?E=_9G4(lxvQDb< z7GWaAXfc_lk-X;0!_~IUqMAFLcgbdYidJ6*DV0!-Ig};bHc*uRg}9ZR9-E$4W%ja@ z@VwBASNd(lZU>@~?U($+;{4lM>7xeJWdj;C=4T0Y39Fd>DzO%thl90d$ut|+Py?DK zkQU4VN^X%7Ep6d35^GbI@POEp`21E39yg#{>);z|zBcm?$h0IT1lwE;I=?Y+0Nb60 zKQxo=xqN^Iz`4w2&72Pj4U{k`?&-lmD~6-OL2b=RL%ho;Oz36~&cssE1bt=!=i_(U z`pf=bfThsiw8$Fkg&DIY--?4iEoLI}ps)5FR;==pIOey605 zuC|62c55+l!g(d3r^r;1|LDDM;$Qi)E_!)_?H}xz%hxfD8Jf;UZ5j&a*zb3O}i;WVUblTyK|feju>1 z3D-+xebUvjJZb|0*2kAShf-78Kt;6z92K`GhO>#-<*d3nEHE*F0B#go-c=U?nwfhw zJQAd|)2j?ks(r+Yk74y%{beFMyW0h=iKsSh;&sav(nORSd86m~m>yROsDlh+c+5Gy zMpL~Kf$E$pszZS;y>~Q-jyclUx2a`xbq`@6-JaS08Pm_u0M!}KoY2TtuR0;O1|91C zkzmAPm}qvl!YT*E?002W+;~3jhk4Iisv@)Yaf*i1UMcK13f~)BfZuVVUg}nMuWJ)IaR$Zpwn*&1`O7d z4-mqxkIDsB5dc5|Y7nv}lG`cJq@#u*KoMmS%@qt_CeRXzAES4}WXrIN>HX9+DEv~^ zB|#o~Ab7tqCXQpcH`8r$CXu9OQWQ5b4oC-0eTq3Dyp)$z#n{&)OY~6zkMevT@7x=n zJ$t}IzeTIM<0HO?|kPSyz<(sfKEJr z{skT%zrg3e`>lgrDLwklSP+amue4_SK2=Myk2clxQVy@*m76ZYZ8f$}mtRs|w>_sS ztmX7YZtB%7_EA_HQYAbMpNJ){Mt$9_xNpWju`APS*hFnmBz)ZWB~H{9=1APo#ZAn+ ztn-~9X6`6#kvKSMG6p_ZTWe+jHvrA@QuKC*Gk1eUWZgUm@ z4561t84z zRa>XawWRfP&nHL{-sokW?yt(1%Xi(|lx#3yx1`Uy>X#oyb^lA8z9UpmKP146Z7`GF zP!P0JTkQQ8h&)(gWT9YLqd7pOZ9b2g>B!@4AR?j=dGFu6$^2&~`m>i|059@*2>`s{ z?3d=v^??5QQ0_mS;*?h;?_lSm<%fnc{yrMb{JScJn28FKxHV3bXC@1Tq&tvleQ!M_jJb#J*~ zEZM}7+4FGoJ01mj!ROnqH={fn1< z^hyS%>njc~MaVf=%ldhqKb!X+C6e_1r2EgI!3cpc_I6zz!?TL>dNM`~5FvwLTKPjk z0d(SN5HvSxESJF22Xg|gM;tZ72Mh-|d#pL;G~z8sgY3FphL`w*+~{!;W_@|4l7?YF;<_dfjuAARrvKKSsb_~3*0@xvef0B^khI#d;pkB|7`i_h`dXTK@z z5Q)&5st;MN3S0fc6iLF0x~2>SXI>e z+*S9@@mOw8%W;4B{+b-u%Wa^bDJT^J7VmtCg)aM9y-#L(ug}f7pD!utF^)#e2wyQ- zdIO?ltb6`-S{OiUxb==(>$p$5DVa+(2S60Blx890#oKSMTi??d0oj9D#qdozWMz&@-}361Dv1RyBI%{RJPvd=|{QH})@?1hcwOad)^?&|_N zEhhn3m)zETvwhG?(ZXfUOqLatB?n8MC2=T%>KeWI&Z%c;LCoWVLur&Cq;a3ra^e>a zsT*Y89KTVSTh2X#fYlU$2rm^YFE}N2t7RT*Sp+N_RM)SEibRaQw_hmxxJ)?5Bs8Xi zBUUg*%8K$=iE1zvxXFFF%7P3%4PCh`W~8yovy0EIf?+8}Eq6tW*mh6&sSYUAP=)Xg zVO3mf1HDgG&UtDUXBUcgxjQZRTcws%nfx?j6W_~|r(Nl}+jD&*$uqg15Qr#%f`%ql z@5x=;?nJ~5iYA9Vd*eUUfAin^bNTCk{jWXs2LIR|B6TJIlXbiV0ABDRs`zXi=n8yC zWT4Tlb%n%95iI;Eiohv_L0yy-9w@)<2*Yap7qogk?TO>8I_^r@@@F7_e1>muRGvdw z*WAPEeKhHW^>hlCFTq@|U&4hrN`t_c1oMzN2#JtD7cURslU;db*rB*F6c2SOx4-%HvUSGto2t`Ot+%F1kU}{wq`jw7*;~VX4orA5JoiLDJQy8) z!VTbQ33{iq3%Vx1t>{l9SV>A zcLs%Y4<#_xK~zL=wt+fr?zj)2**u-Y40QJM922*Pj)TmB#_qIxiU9p)pi8@XNCx0Z z2(7DIbMLwvlL}zW0jSTNIRg}#V+0^*4*ZPmA08%=MwsV^5c6W=1EOAMsitlfE z#v7OWvd#p6FwVc&Y|I#b>-Jn4KxQCyG->D}c(`$%N3^gT;~b-EJWojLU>Urrist=R zlc|R4>Y*F}(0j)#&z?n+Gy^UM#&L)CjyK=@8h-Ho@8f4b`w*Xe{IMDQ@BRSaeCum~ zDDL+IpMU;2{^BqG9OD?4sId2(QyvnN`uUgX8#Vt5r)pBZ|MI$uZhuxZcSUMX60i#D zO1&)<-6HuadR(4dJo6>2NXuCZaPV~NzXa{rfMXP(wd{?{eS8mXvVOnpMvaPd1q}8L znFi2x%fg_|TUz&#;w>~88koG2bH++K_ShfT?Fys$N1wM_=H8HubvJ{zE#sQcBuGag z=@clg=d%4a5gCaBINe_&U|M5dW^MY+cjKTz$gTw6DZ5S9PFfY09mjns%Bq(s37t_b z0E%KZ=QXW~fzGTdQ|C=h_^57pN0^pTmk|Ij7g8@JvP9r1YAV8M6L_!m(=4j;36@$49`i0PK$1n#(6c~1C%N#SO60- zpH_&m=2i%yQ$-ZPBPqr|zb`<-dQ_G{oOlug2|gfy&yLE|i@wb{CFPzK1c-TcLOUhL zY7ro7Z=8Ud?S0tRV3Beh!ogOAcC42RkG#*h#r?m@^ONj!I*+AKR^nTL0z;dA{@UAk z@9nSSkKuSQ55FhpB2^a5y!-HL)^tL9Z906Cz^x&op2_b&?2U2ITa({wVb)ND7+J z(6zh-I?XE%%>++Ye$C#$skjl`oQkhu>~kdPQPr6nVQXTE`NjweJL5xdw{FI-^$`@7 zoj>vqxYDHx9AgG!!i;Ohc+Pfun1&>{Gd#U?Doc#>Z#mv2yulC-4L#f_{TAXe=ZLb9 z0!yN2#RRC>7@$#WSWho7P~#;Vl^QC!lQF%Lo)K5xX|PTYF-d1=Qm1RF#q>NeXN7q$ zO}{L2RRQS4nCb20_pN0{f2hyX2ova-UV<<4Ng#kymZnNDPr))@^cWggfygBRpEEbP z;n}kX&(?1&jQjIPKooDj@dm#8-S6VPAHRoBKKU4*e*821;DAjhFK|EZU~;P78v3)IIv~d;1B-WeO>|LIEwn)+};gmVQV(y_iHH z?@i&~g=hMG(qLTef8Mn4s7^|K<$JNcds5meMT>g7+^@Df6zj!iLkug^Qk2TqsxK@RDtRMm${G0Q1Zp|sx6L_9*CMO_~`6p6-c-DYr zz1Ad`RvI=v^eLCj8_sJZ-x2Q~MtHtYgmsm5n!s+dha^WYB@350zpVNwftbakU&jf) z<|{awhI!m~kr${B%l9hVRfOKG6DZz-*K@m9r2NkfCYc#w&%3P2e%R%D2#{D`>sF8; z-Es|Pk}(kvfezsQ8^I^9yn>g}J-iyLyBX`hu4v>UHF)+}TvS9jRjjj+TsESs=T}}Ye9KW?FL(;PSRT2m zhwHug38(|X{*x!#4q^Gcz2)0IvG;Eo8ck!e9#fgn4=i10qBQn(&*97jbwd}$ogM(K z=hJZNG}cl{u%VhNt%*}PLkS{18?X%gV@#m6;-$1Vm1dOqCYF(!W9#w!+N6nj|0kQT zIuP_$K2JRdfUyG1puF8~&~bMIoJwi-Po=F#Jf~RS&N&jycjc3S0KHTK-p9b0&g}iH z2;e(u_e6$4eOEIU39>-bL#Sdv02Ah09d2ZKFZE=LBNOInF~rAV8eEViM&T;hX*((@HbW1OQ0&7k|ev z;L1E&*K|k9p&bRR1d+*_AKl>1${`?cX|I?q~o8S62UVZiq za~}BOi!boI-~I-_8N(97iM8q0drF2dIFPbH1wqxWG9xfGUEV8{I4X{UO+m|hhoZ~# zT^p_9R_eV^SEmeI4f!Hp1Hj%DMLA33#Slf%qQg^Off{TtwaqCi?g4O|JoalF78W)7DNHqNF}MfY?B}X*VQ`2XK6$fI?yDrqLtfp5e?*;xkH`?`>tqsbYXKZU#w3 z0GV+^e`pglPpIj|RnoA9g#;vc%VfF9*r}mMvCo(K^x5y;k#m5B&b0su;4<{Kac>Tw zlT)JfqsbnJBVvEu*MQwPclT8btU6Q7JS}>_I(zJ%n&S|^IY37nGpay@-dNkjybAnw z#y-w5ugRJfG}OsbD&Uk0^CogxYI3E!S6Sv-ag(x`D=1|hqWOpgJc!9!!VPxEPfhk5 z00e)RoX7lyISXWK>lLdY+a;%FC)9@p2wb)Ibwm~b3fT6~a!t7%<^Sou3&^zS-Fo#7 zXE~*s`|mz9$$i!GH$y0V?&cKN+YR&Y|9k&8%#Z&yX+QokULG&*cnJW!;G+TfT!GJr z%D0!@Crh?-NeTf~hgHw$Vo`u`Hn55K)rUNF5PH}txQKgh=U<|o#cNRLq5C<1iQ8wV zd}*7nzN@13sWu>_RwDW5zQ985&o%`jUVn@JAPBOKc>i1DdsoQCDCtzrnQV_^(juF_J@I}V zIag;I)Uz-jbSmzL(f+GfWE`ZyruRq$mS7M}PDn4l#B=X=YhPnFZO#6l!*eajB1cZC*QFqlQj9` z`46T<88HqjllbvHEvX?r_lP_NbzJ~VBIe*iP*$fgNNC&x1z@qc!#H?_SG1^c!0`_0 znb_q3Lnb%`I>~^bM=)bO8_eLp^;^kVxWywZ*%cX!D z0*mcm9&3iHEs2&5DXpp@8T1-PIoNPVi+Fm!i2tLkB;DL&`?U@!_dT+9tnYJ@C_U3f z_oOx7Fa z`2qp(|D>l35|nn(bC3pZyHxiIa#fAnFxdSkNzD=4IqT$KwbEcHJs|@)HTEx0tohnv zk)p?3KlkD)l*!IvOi)2mzua(8|F`T73nig27wR{)Uu11v=wxSL)1aY9Mq?8}9#sF< zvws!yPyab>yYPE?e5J=r0N@25(t!KD;r`hW{hr4|D+|m}8K%M##w@Gr=6#StK0Iaa z)~v{H7&WC0YkW>M$j_-e7Syl42hK=g+%afH`lTYPUR-4Rm%$D=|GEiee`jurk^Cq6 zL!Z87c@u87b-O*|FDvZNxVzeOH91sFfP%s==cy%kn_eTl8o@LO6SQEPQlTn8EEH~4 zE78Vm`k>c%%rGL4$nq|WVg*>0;h9#mzh)Z8(4^}3X3PM_W2?u@=O!&}^({1-`UOI4CTyCQWjAQGDEZfKRLJGgKB%XxPIL2^=Tw(*Z5{MG<>_Jyo^Vl^J z3{}fF&?VAjr`h9cuQ7x| zouW=gU+r;mJ;7QtA@x1IJ<=@Bze~&NC3Cur>K=}i&7ko=V?NDbU9e&>4ZndI;O4R? zYK}{YOO4sfv;|-iQW1KobH>m>>28&gR&O$`8YHok00^gihGKBu#U55o##M(9E!T4o zE#(Ho9xRLlC~KVSb~o4Jh*OmO0tB)G%qp(?h4)8MP@WZlks=T?zK`E*O|Z)8+3Lp} zFZQV}^9JSf%rRdU2HQ}^!dW6*wzKFYx()2Ykl@X@Jzp(2e}-(UG5ebxElb-NYOtHk z;<;j&_>VtEC4yiKtYa!gw$wJ9c zwzg;ALn=oiB)#uD5Lwn&5$VYJTJG6mKhx)5sQ&x^-~R&b`RBmbUWNg@$m1md@Pd!2 zz-uz`^*3KpX)P#iu^jbUfJCinPf(RgRW&48FBhRBtm<0ga%YHbF?BD{&jKHo-%_38 z-75-tCB*hbd+Q^X7F8f}+lcZzxwwtSZ<>$2=!?saC_S})QE-LS!Ithj-%O}j#VNVz zS(lcWG!8oiE zb8n*RS>4AR+M2lCs@j_1IMT=;V`hmupUY;zCN0w#2Myyeqrcy}7@5`L;g@>#-tjs# zGL6sDUV5tqPK}3oZAA+OpGxFpxvbo1r)R$p1ykXxD9w)P^i`c^m@D9!er3Lvc5y8X54G#|w4&Dqr|NIe61aG|YCf@z=kMQZwKgFN@>7U@E zk3PV6zx!Rh_UdaGeRJjXBp;xBN&KLR4S^&5I`=-nz()Z~NJ3%v@OG*i6uuJS6K zWlP8t7nEAn<;>5*a%v%~W1Z)n7GRV{PkHc_-}_2xF7LT0tkvAsr|)z+-`~LvPAaR{ z1hXBkPVkTOo7P69Lg!|t+to!)mgVO%`LbQ>wkKt2rC~734HmFHhY)0;#JyLjAtrZ% zzkDr;n;8Sg)~&#RCd&a(wsB2rKTtr>ZS2wVoCZl<#3|k3@i!DI-c_qJLSPAC#Zt(t zq`^(#Tzw4GE5aC?jfwY427jg5o1;9-gh&tMsviOV++a76(#<4#9DI#(rS>rmbpCCq zMvkXCg`uvi$%%t%&*wfLz#YgWVEAEZF|nR2kdaD=eZzif#mHWDLp~>aCkpP>^OUC+ zeQ#M&_cdzb`;Bt1O! zT^NR6pDKBQC*aE)$?`vDlXt~YsobRjWu@v^nr?bIw+5?ar?}l%PEQkS5nvA0*6oXN z91syv74^h+avVb~QC@>PsN>w}@%3;JM1$hb_Y>7QWZdm(O zEa4g<7M46qP!t=_?_f&AglPBkY>_jUK;$#ve2&s!En^@j%S!5EJV3J_4o|w+C?g8( zf6R#nG2`0TQ7fhVEaYW|e2KV88U_yjG-;S^;&!{?m1nQum1hq)?g#FVk9hs{SMkob zzm1>#_&xlUKlv;8tAF*c;-^3P3BK{Ix6wq5*8jWD@!Mbj#!6N~(Rw%VfPGv*y;Z9D zIJ*TK8P!82Pc?Um(|tk-TCrG(xtnXu*+zmzYF5DMY?#Nbp3qZ^I#}gf*w9tYU2nwd zG>Z~ljV`_kmGAO)1TLtI2MAD_zUcdfGaRybc?-$+T)#zby#8e6{u6?$oVOY$wL7zI z$$DBi5GDSh?gl}EDDFFDBQ$;7cB#*G0hCv^WDeOHu)DM32fKS`#ZV`(oC6YhJwgTq zo5~Wkc1P@njx3Fbl(VZEp|tZYZ@%XKSyRYm`(^-1vxYhNC`}Z3Qyv7Ud!a9pwi0f` zTqC9DX?Vxpt`1@m+8mj~Vu07e?dH8~;MQyn~vod#UymiILRSMmTP zUm@mgOhM%kfR=ass3reRT9_4Zt@C4TGn2sPa*!hbkaIyIy58R|!%D)4T-Ku^#_~v>%`{o#IuL_Uw!*~ z_?rMe`u%VI^7u!1yaWJV@F4>9&;5hOSD@%IYui>wUD}J$eSz4C6v0$b+^1~qh}*ib z<`=Kzg7^h)e7f7JVw!SuLIr=HV5?;f7f-}l%!Sewa{e!g6fUT?Zuj1d^>dG{C~nQz zS~eyM_8EP+4Z`wy{hr2mWX>%HOlFzq*rvdy0mT?>gg4giKfNaW9Oji_rgafdAgI%0 z?xd9!=M87qe#nUbB<-hf$&-N~nV9;0qzPIWO_sy+09AluhzR)TM zK?Rh=gJF!?-ZH&DeD5tX>PJ|EWK{(8@9?PgwbjEt3R9u8n&ie>mW)`XV>q<5S~d&$`V@f-`Gt4YN0PtOsd@UnjHooC*N z+pPmrqwUa%IpKzZLgW@yeNLn4ahsNhp!^`2@Q`^Q$fB5xSye3w9D&|%(H5zesu(E1 z{;yh}G_UpGc@5M{GD>SpGQ!&EIp$I|p!K#WC6RPc+#}jN*`XL<^?1MI{`@(<|NZad zPygiS_-Fqce-(fFXMckCe)Jx`_O-8JPQ~ZH`yGDui(g_K2P}62Lj`X6oTw0x9?&b< zRY9#(`erdABO3uic`Qa|J+dBoKHwDM6rx3T5xt0`yvb9t`YeIZiTK>8)~6n{Dvi|# zF2af!t8Bh%NxV<}y#&6md0^5AM9_s50;)-z##?|JesAW8h?@3pbZd0J>hwLSNkm{lrY8b)J)tJSi~ezaD9h=U zusH!5ddjUiqBNeU8W>kV@G!|`LZ|nd&iIXhN9sActe|*5tjn+V&<=7eGhh84`_i22 zbHbG57}X0V!uY?+Qg3sj=r<>IZq2PwN#Hx1Gm=OK2s9I&Nb@(uet?{%EmZUaqNyqF^<_G_)-M{?8u#5Sn}ZL1tu5aVJ*7Nb55(n32E4YW9vpNj77 zM(3qx>1jhcv!$kpIV&-g?7GP)!W;k!#roVs?-q9LxH*c-9)>Tz;yLAX83~KYXXJR|POK&~B#_(zc zMUhZX?nf;zjN;Ci>=FGj$B60(OdMrNt`hj*nwegiK%Jgy&&nJN0;A4T?ISI{UMrq) zy0J7M%K%Elyic&i*JLQfG-B-_Xh(aDg#H|*yo&?lVJy0^}zK0d>791 zAMo={*xSxWhlEfHu#3J#r3P`APOAt4p|tpN5i8>sE3%FsnlE zJ^S06!F4pJV%`rh>wnhsE^BPgr6zm#{0wy+_10|sJnzH(@xV8}@pb(7|E2#n{#*ae zzm9+Yul@6Q|EE8}tFOF<`*Fu-Uwn@L^6&m#2Z=hm*m{8LJT(jXmAuG$-0ONz$>R=^ zVz7tVx(g0XHS^LgcexVV0NG=^EnN-$h}pa_hUF{?V=i$b_&x0F+?(iKy$oS3?O)-M zY%W-U+MAoc9gQAsc1)|z5m@PkyEdo47Gg4HF50P z+!`805mN5Z;B&co9ZbK4=NDmFOz{k@38s2EI%%;N^C{igM>Ni>-9#XYvL@6OT|>v# zd_x%!7|HTtw$zAXO&e9SDtQ9wX`rjOAsJ1`xo`G(fED)B7IZ<`=I8FT{a}}@Z@H)o zljqbbOhq?H-K(tWWe%+OT*kR93$hWHN#xgSK^JI5Ys57H)fp65o&M%r^{`Sl$p&m) zGGr~4@Pt_P3$urL4rJ-J28IO{875_g1S5~%XHE-dzzRG{Ic{omTLq`&Axm=DvpkaJ z|K-WwvQ0I=%4$HbMt2!5UtdBcBCaXA?7l(7;IG4eWyYs!pR;QT7}AS-3vjh>#l+wC zh`5H8O~@wN0dC)LmN{15BW8_qeU%WVKJm48eh`eJ+Nzgf0AKC#5&-yWk5y{`fM>7b z_NPDA@gM&d3d^<w}<(4h{gpMv(T)wF8 zKNJ}E_e3pim*w3qQDOf=E`{iRWPbo9lfrVtU8t3fX?ycsgl3;#Db+N#fLfN3ICgC# zv;BuU?Xhf@Ty9k!qEwH2Zq2|kV|p@)AYe+MyGnYOFrMh?m2NT6)2Z^U3vRcL3BqV2qIo0s*H8`eyAO?)&{%dZ$ar@WhA0_BIVqb90aT z{eBcATHFxt(Bf%s-4xrQPWRMx2^me??k&t<&t!VT6sXp_W#{iD%kY?(%{Tj~vM?>o zL7LMjy>Vnt+VGbJn2|VuQw3aT3!KHq9bGj z0R*s$z*I#;(wV1wDmez-lj{w&{1*1^i2ew0ht&(*aj`|5YsjZn>{UZAuP>r&KQBN6dh#T7$1n@FD=Mq(WODCveOFp*YN?_bFJWG? z=KQzjbt&ix_WbLM7Q4opyeJ^Q?0v2>MFAt5zB_EOW)<*xXO+Sbi}+y zp%c4&7ABqa_do@9qo_mBRF==JMCjp!YO$hG0~kW(|#4HU+Jg_0!@wF1AsLP`kO{y4jT z(OzR2=mJ($P$1;+_8$5Fo7ykA9{kO)|Fs@xNw`Bq3pn(b(y0Bs5{|IWsr*+(kT|`p zzmuR$ogq#7<8S;6f5~@wdHka|UIGAL;c;0i zPae_nYdp-sJ!^&gJZ3uMSW%<^OTulptbYks=0G)cFR!Yo=>;-a7i)aACj-%wC|~bX z4f)VWe{bKh{XvBiJ=Kvt$97))k=kjz=N=ce3Svb~jU=H4voXklEnW-Fh!GKC=MVai7Dy(mns zKsiOyYS$}hvynj%v~mx&86!R8Ou9!}{Ta5#YQV9;e@g zCGPw=s$P)+q43{s7BY}Yy&P!rHnGwd)aHc37!U$WPDtvpfi2yarJB*cG~8}C+#YV; zZmZ&OX&ocMmqi)ns#YwCCKt_Du2nw9sQTa(sj;H8m3B|i_Da$ytSlDS#rhhRqQ3B^ zLWY#(6v}*1^hLJtECPPpyU~-5lwW6bc$d9ZbeDHyzj6949w=EZP6K_tvy^tmo65C3 zugn{QJv7k74SS7t)eH{Bz%OAmo{b@raxLS`Eh;99-$xKR6&(0+f`uFV9wwwNE5gI> zU@@uK1|kZknryU$dMtC=mN!y{*3cLY4Mk(_88DfeONN|j(CO*euy}pN5Y_2?=c#Js zM^%mTwSXmpwLMFjR{+u=o?nDL!%Xwm#aiS>8b-}!eYMQ%J27&8HB75#Ji!Li{VDsj z%0MrbSabLUEnX7@piVYv{Oc?g1Jv8rGGQraQ^q*fDuGCzgztDI{9>8M>Wl@>)5V}i z7=*RW?2CFzP)s=gcpV6u`TXSrYI|aC=j`Ttpn$Y#Y}y40fJ*3ICe}61XI6j5t1Xv6 z!=j&_p_Cn_)s#6`F zWM205e)XaU7yUMM%mQIVn40e&1rIqn<)$_Hu72%8%I7<6fuY z)rrq$k6-m_K-i#h57<|&wWvj3yQ_M8nBZ^SxYTrz=m>RseeS91C7=MtS&q1zO30i8 z4T9!b=RB8yw(!)tx7`(%-dfmOIz3FFK^|^5RVZ3-qEl5^B>);F2rW+AJi%iIeO%S) z^HQ|$SwMr{yL;)?+3XV{&eM>R5#Ki)qq(}$%(#DKB0vZtqrUa8F&-Ah$3qTUmk9i%(XIzFM}Y6fB7(ybGr9q6 zCBEliNcFpuw=n z1YirgW%G7xCoy5W!>YTn|ntIgh-5=;*0xpJb!$S=5vcx3??Mz zkgd;+LRu7~T#2i}8OCzOJ1=?hvPrJEL6riR@{0FO3bro&Fm9u9s8tEPU0d!CGlZF=f(h=bamXAp2<8&s%8c_-V)jL+ILANUqDGUe#SN215R-6C z@J0_h!B0^y$+{Tupulv{Am5Y1e>0#7P9WQ< zs+fy-&OXjtu|H&?(4Sv4r-m%o*A{KEgx^2WZE-W-Ym+Hl`;_mxLk=0*#3#iVl?SZ1 zaHTXrp`3Z116Wyo;oem4Kl*<{@GoC?#>?X?J6-|+FZg);V&L!mr~g16@C8JshoEJa z*(JV60w}|!%BAaO9LJ(#tr{7XxQbrJ?Jm|^2*MDa^?aNcKU;ee@^Z19SB0~_*Z!p} zLcCbFPaIW!WfjQi3>+{Fx{_gelMz&jr4o|Yy8;)T$jYTNiC#2g; zW$ZmLJ(Y3VZwxnJIlC;Y2hB{>)14&r7T1z6&A9{{}e#3Db5lJIm z(EAM#VCazwK@&9Tn8&a%5BK~ls|7N|K|E)HBmVQSl7B#E+GmIWq$=>P2r>bo|4#2s zgbwf=&`W5)-8`Wo4cRes2H z(0g}A1R_TpprMp-+4hzc=+?vmwp1XkCb|J5a}K{(N2~Iucr?$pt7hX)qB$i5eZHr& zo=^hyLe>&&*}Ey_htKOpN*Ti3q{u5hi{a|sImxX>!CRor^=(ocH7rI-Jm=1Oi2l;F zEW)zQzZcx1wm}N`vAu7+P&B`mUz9SW3E)<*)!sR+w1ZS*_?!|Hpa&-iCt4JyWidF? zfb{-J`^)B<8!uc7E`UTJR(9LkphAGI^m%g>rN#T2rBZm41PC^T&g?>&@f)BPmIaIH)u}Qkdx4bwmc~CU+#*7mU;x9M$n`f7dnTCxCMVJ; zro8da^AIP5tmG^(2TFSM!)Bft=b!Zb(uSCo;!$=Q^CFgxs@F2%9}NTt`*{pyIkNVNup6CB(oR(157mGt0w7UAL)ifCl4>d1W}ymN3W=nFY-h z!V3jB@C2&O%T}tC1zrUw=cCWxTup13M}icbvI=ILNk^EElSqJvQLU$~MsSWzyhNV;={5b@xDuF zi57s770xe{TE9o2>E#+e3!8J>zvLmh-Z!b4 z*VmoytLrJt`wPCL9i=H!M}z+r;rwKuWdfrb|0@e+wrF6`C>bZcY(De^w*njlh%JT1 zph;nOk0^~;sAq51%;G)=YslNV_xlk>T+~JuGe%i&zTZmW3IXnqhl33R;+d&g(-{I| z82Eb`Mb*+jK_`aCk9)>@nRAK=li+2HX}h`jniEi!-kYdS5fN!kWU7j)>YP)jl_S9I z*1>8CV@z$msZP}i=Q&v|ANQMSpDv}wFDsUP2e()f3o5ZD9dw!innI#9}s zqCC0$&J8lx+yxwm{o7*W<^0*eJoPrQkFiYaV)$wSNUoemTG}S-IkDbLUvj=PR5QVd za=%RjOzr&~@Y zveQu1#oO0Z&_raCveuwFi0~UYAsl$8ty{hL7L12bO4W>FDyn9{BPi4ptV0imKgb*g z9Q4-Qh^EJ1(i32vWuU;W9ZfdNau2BYy^>-toG zCI2|qp4XwZsKhXsO~17SheVA2tXmIpdd>g>324FRFq07G{BGV=uIv$jnlp_X{%0P-c>ih`Rii4x1VZ!9`9zsz%m7zCc9}NK zSdo?rok*3V<@r*QT`sb^Ji7|8%|Kl#|5wKME`tjAR}2i<1=FJAZiexqP`e~1&03Qo zQFD6dvw4a4!IC`7#;$t)1C+i1LIWxEPuvQ2l{39=<+-JMgZ^BFI(baGzIU?O>T?oO zT7m#%-CNR!*@6j>az4yTpKdgjay=1%&Kj8Y%xI&NNVNnNVr+$C$DCWc~2(v@dNvDRQ58l313kkGuLP=(^TKfWa^Z zNZ1(xeKGUDCkAZrznL5r#VaoGIQwKTG510y`jf2*4qg|wQdC?iFTwGi(cg<5T*98P zL|=;Le9At}^_D>rIp0&)^6}7|+hMGh(u%RtivGuBrlFUGSKp7Ql92P5A$3*n@!&{% z#|U+4f(cAg3#8>tr{Wc`WqHS_CIELzxL)Mce&)5P(=z*{fFp~1qfFwg$&jH~+mcrs z2wuLUqoq8QK+&SFSNZanFk9=Kw0SKH=$7jB4nMTO%dU)mmgx*9QTc7@IF}`h(Y_zz zJvK>54& zL9v)GLm_}CYf`T(Zbv66){B`!VN%d9-aTXK8bI@_bxaS`jETej$M3^SHs1FS_bT%~&`G zCmdw3cWh#@`1C{*tS|*MAb`j>kQE3FcwsER<^RiesrM(9V=2mB6Tz)_PZUUxsg(66 zizhcM7SmO8#kyArbuIcLX?gWR4_)e7`@~o>w^s`M#j)41&Og^{*5|UVtGr)jSt`ll z0gC(9Ht(i+X$F;I0TgZdA>Xk!|I2t;d{&N8wx=j3#ZxY@tmgK*Qm*rB==m1$a;FYv zRS&$BR`6UT=)q^UNRXG_^(Mq&`E+4=5&Nf;EeEi%@tm zuCtbSO5Qj>EIM{YS9o#-Mt!^zxZV18Y0PO5V!drAmfx;#VY<`WKM^nIj>rAh3+XD= z(J=lA(A2(N*Gm&=|N6&E2EYqG+AF|!|Jn_I z?>`)(I=iXY6u`bUL|u|xY$+&ta6?-SV*$dGk^rDvaOGa4;qfBbL^07iIVo_7OO}h5 zU$2h6-LieVAkX`EbzcZ&N=`Rl1qI)fPufsa$!jWLySHlvQTOssa@uBcTo7Y-1$6MM ziW<|)9m#jx_DBWEyia+FI!)ZE&aN2`&l1m^0huI>HQKYpoK7qA7M?!=?)SsWOgDOh zR76Ed^K%_n$5^xNoA(99SW1J>DBmpJ6xIp_<_aEE{Ms{iZauRy-P(Bg#C^ouzL~k?pnw>H8KK2ADn3sk`?ZG?tSgTf7)NY7G< z_(ew2z+U8Jl^Z7uF^z#Dn->uA3M05wpjK8^UgI|@KtUDd3)E1IG#aJxNfxs?+ffXj zt9AuDVQHyEm!^^O#I7JTCCb%OO#)rPhZ;;P8^p2zG8aaOYQ)uBE7On;slPXKEt3Gi zfgRpHQIup#08kyJDO=CRg*m^9*=lNomzN`eh?zzd^Q{~nip4~U6S+1bH%Qs|vaG8m zZLNuLxnDIEudG4?KKn2#(D?r~Pe@9+BjWj5+;cg#$~Sgcxk=EiS??3kPH4>mkieK0 zd67Lf%RJ4oiWqW;g-6xA0h&fjKE4?8K_5@!Ph&nf%rZ6Bc6NOlm6ZuNOXa6p-N{Ny z*6Wa^4}nM+%z48k(OT&Gkqw}p8|QN$UOISL7g~}Lq&efs(`%`OQ#t2|q7vOappN2JJ|y^-8sqb+;FS8Q^0 z?eCLlS!P(32jzwg9VTLO)wDo7%8!!&B#W*zGhxHn=jQZELcz|fNEok`?L~r@167ev zKKv;!t- z$~C)J=?ck2+2Cr%{xo=3luowA7@F6P=$&^5tl#`Y%u`ymt7dt6hriUV*sj=}S7h>h zzo&nBD!!~L;g{`PZgR~VI9<=qi_KcgmvF~>dMCI+n-{c)fkXqF9*r?(WWk?q{AXSG zeE4wXj~>~<+yVlxY44@eJWhJfDNs?yzH9WoO`3k|p?9}tWa*68$h-pP^&J>vh^ops z2EF3qM!9(k08tf-`v=fkmwvk`;FSp$W(2ZEb!^svPBT55Cx7d$TZZIdMilRlm@+f& zgYRqE>Lp`jxq+L}Yu@$lG36IqDk4hejMk>E#CVzS_drPRYrGgnsD?PwtPTY^f+v;m+ zk&Wv*7hl5&@#IRD%IVxIagoYW8vRuRS`E3=lh^xGDoL)LyiiVJatV8S3W1eX0b6+i ztL2{xW~Jb}pMGHr6j0#ud9i>ld;BSnSSw#wZ4mK%gg1T-bI|P4@I;k0344ilG6wv=jPfSffu!M%?>`1MIpOtmM1QKqRyRiM%cnm5G#kr^~by= zQ1t|XHECp~hD%9LfWm=B2PYcxy_;C|43-t-{$Qe&=3VMGR+Tjqk{WjCYcmS(nc3%Q zTaxa~yA^=IaSHRA=oAkx$oFP{6`LfkFUBH!l)y*;2eFl90z&hya;&`V7fcr$UkAQv zXd~<-M7%t-gquk2>t$92+_C@_d7jQP17|WFQ2Gkqcayr3B7lx{p5-%`LBDLT%efJB zRZTTpSMEqxwNO`8=lQJ6O}JM%^xgtKmBHh$C*ZEK1f)W+vYvPPy8x7nnz%``rGGTq zP@O_m0_LNtmYkiFL2CYenG0WbJf)$YK!eh=SAto${p><;*WQVBkk`i$$9lIf3W(?@ zN*%Of*Q7}yfC}0e=z8GwzxB89@`ywJ>)+z{A1?ub7koV29?)O;8orqK2CJe*Z6eOu z&DMgYouIq`24X>`aT>Z%T(T)-t6aN0)&;wn|GJBddb1QEv)<%_f>r<0Rjh=EVXxQ= z=%yH#{oL$_~_$_IZa#re&w4 z3dCxw13`mtUM?&8$}{ytf#dOrBz&g4p(s>R6gr@_=IlNRMNp@@VdiLkQKXMCPqE}q zzZWy|8+Uurso^Pz`Kw07XT9zwqD-!(3a9Ryk!)dxrhxT_M(>q)Icc49%AB6YOVkqy zYLw?{T7MzmIc$vki%c55g1EIrsupbES z_^BcWv~FKsMPK@muNw$EuWR}1A#O5Gg$oIem5^yNXA>Nl+ z0)nBpZR}A5uz;`$Wt32*#bWWbj`A-TFlhr365#8M*Hl`8k5jdIPkK!lL%wPvL*%;t zX{g6KiZ&FZaPhpyh=Dp#usJEax9fI{32{xfFm#q0UJy!;mS^q&PmDeVk670?n7=>Y zdyzL6^5Mj;tnKtUzsfV8_Z6_?vx-QJ{YvWMm-7CCdgIlJR)LpQ{jSxALQ0+V=v7~i zUax?n>}Q^(_DTI;rsK7&-J6ozmqcGexAQ`Tt_eV^?t?n#wtd$bFvPK3+y@vQ3p($o zK%4k?fBg&0uWkPDKk}mi_=f%YLX7>V+VJvtfyeFP0Y7{12YA*+bk3ejdJ}79fv1vn zq0T+w(c<|D_*E3IB6s_hiDZhDKZ-z&v##Xbrr1$|&7XG+;{y1I*W6LmWy{67wOt#~ zUuIzQTPh7}xBh-qYu2-aHPIoG?iD6hwzzBMz>kHgv$%GM$`?7f?x zk7}SOvJ@keHcBSwi*lE$7&8rm^c+$@)DV`L?9do62H1q}D-^>@T-96(MeI;{D@L&9 z_1WE5s_DM?w|6e`b92zgJ(BN`24&8>^-M_Kuu8=a)Vh|HaXhk9IKH3k!Y*X#baV~e$OH& z)XVpA{%byRs$xb|{(2)ezm~3TXux#uywRR=<^}7=6RnnR-VKj1D564Ah7+(28uFD} zq2}_S94>JJ3iV<##QV@pS|hV(LLOyaSN%v4lZx+J zAdY1%N2_IlByuc^{4NrE)y0Z#7dlo2KMT;i^l5Ab`Ma&PtB}^J{}E8)ymJ-jGEYlY zfA##Gw-w-?GqJ=#mNCAv8>;7CXy``|W0|Y_QP(QRZ^;L-^lyEE{Pu(y2JY5FST9(2 zzRK{*JiKdMU81&|612Q0%6swb)iAnRxwUubG0zv!B|if@zbYN{yBHXd;SC~y&Z(7x zD@dj{y`Vg5#&!1N9Hy@ITgN;m!|gnl+dS{n!2%r`7C@z-GfaKVi6)-ZZYp$;!rpP* z4?tL%z)J5q*bpJEV@@%Tc*C5d#IpMQKCDe5g4r9!RP?S3!$GHIr^lRZudzi9+)&P~ zVcbob9;7cK%6Fm{Yr?$aP{nZ^jF;DO3`i4c&8~@x0XiY-ssKwCTE=}1I;I$NL#Nfe zKkg$E3<^}{1X}lU2|gQ-V?eqDIK$*pk?hX%5ln8>?s=cnk~^~eUW6w^B1n(r_qH_; zH860<-eF8lU{9wTX4Zbjzr)B42@d z3>yfDK(hV48+o}hD&oFVLt$n@Ljompjsl9MU?D)c0~T8TznZRyYc)SZQu?{>t7Qzr zj4lEyr6oeSKH)Y}f(WX@yx6ac`CHmA>+`qg(KEh(e|lEH3bNckZ!7f?1|HPTsOR8( zK75WnNkaxAjEM7Eqi?o$R3T3zU|}{$*d=M1Up;YqHz3A6t0W*QKU5GWHdHAmDGOYz z3B+nYF#dh1@m=;Nfp`K|W^Q+)JiYcy+tZ{7K$%aCP#7;?XPkU{)+}?~zRI+8LpVAV~i6j6UPc^{<7PU)j7>GRj^K;5)wD0$!@rx4dbfFY!OQ}w(6JnHUAB< zp1)HWvSy_ws8h>kp(${ilsUn7p`qyNr5mWNd(^I9aok5{rw@8{bd-5s=gd0q9^=j! zYRlT6Wvu$`W?u411Yhlg9t2YT);BfIAy5Ha|-0sY}-##}nQA+BUuJf|lv zbZR?2N|A_YnOdP%;TY4B?QzEqL)B|mmr=dlx+g?DihUIMGBCUU z7*j-q%+aW~4UUO>0WEG+aonx8{hZeSXr`QZ&t1^k4Y%8ad)P;=Da|=f3ct(*3!@C6 z0|dQy4|!uz6;9+TDZck-@H^ILJ2Wo$8$A^U^+Ww@$4Z(2*Q6SX*ey?t14^DEL%Rt zwWq2h;q^y6`%);KP0&r2JS}rhhIv)?q#{PERbXQSRE^OS<)+~`XR16UzacHKR7cXN zk9iuu9R zPp#Q34b63SvDs|zegQ&kEqi?Rwe^zvzH;5Bf6|Xv0sL8nFDVx=21f37y@zuisuR%tR(4OYMejRRNuk z*rDp6JKd#c+D0=>0=S-mtiX5J<4RRDqWW@c8V zcb#bLZI)6evnNw&qLFDvPixiC@b4M_-K_q3%mZh{Gp@XKiwg}FfdFHYj&1iElX(Fo z3_Kc$#sqLrxF%}=0FL8;&Jlf0Befp9kBEEhi|1|((`M$FVVFj{rlPs#&*7cZj8m49 zZz3?n!E+h_czE`J-YlPhh$R>_u%dagJ^RJk3u@ql4S0)vOfcuvIlV{cL;?YxD6nbD3PW^9|aPM}zS$n2>^#Pk1ECDHv8H@$ywI3K9?}+QyC_Ttfk2w|h zJMV#rKp>4CnqG7E2`TceH%MEPcS*BEd&xRcFdsz%CJuP?wvKl3oODm}2RHcZ8f9L9 zpe?(zVfq+ze&)JU2UD!?Si4(YE=(l0O0c3Ds_XhKbp2uw=`M%%EqhUh^4diie?qm4 zUy6ZO`n&&=E-Uc(~zug19M++2-s$_w54u@W)0Jf(h{`d`&< zi##WLE0$GTrbE5z1PIhOE;2E?zP2s&WLf(c1)z?OtNLmJ+*hV#%m`WUTkrj)$Euo_ zFpPCysG!KgqLFpp`*?{0^7;Se{|cG2KIM=4U>|%Re^?{@B>?c%9vd_U9QQl^{;xmB zO>_cs2Ulx&i8Z&##lq*ERs*$CbCx@7!0lpiU9R&Kg8K`|`LqxgSL7tphn@oNYFp}G zN#)EZCq*)^JsI{$VSRS`3-Dw+>PAQn`&v1d3!!?-C|*#9g);Rdf_)93h;!bA;j!W{ zjwu6`6>NRLu>6^uOO&oFZO}>2jmNsr=~Vh;1(1e4J)={mV-|PDyN}`Q zf@X>I4$l-17;Aw#+c8IO6s60?F~Ts31b`D^_^H!BnFXJ(->kr}OojFa?M=$&>?Qwb zNJ6yVEWW?>Cgbo@gt977WFmCCna-i%`KLijk9%cm%rR92qI1ghn14|~M}|*W*85d? z1%RpQn1*89XCx;C%)3r1{H=GpuN&Z`_c#ArqD^VQ56{~$W`ZjL98vk@rtKa?V+a)E zm4j8D!}+F;i|8!#ua$LGfFM=(oUuB?Qj#)_DuFG-)>e5E08$twn%S=J~>CEKm*XDPZ|8f0>M<{3;})z`CB zukyPlaoR>94Nfx%HI%%?dfvLHB^a>pAHsY2bxS2G8oW(PKgjyLTs#FVs78AQFW|W* z3c!J4Sj%LaweU@n*>|d2R|$T1p8%#=gySs%pW0 zC<N1qm$GRr-P3nC%pLv-x zD@$xgeQ^1=>}Ow7Dz?cE4-vfa&~Vd-A=2*vX6}E>*5NjvE?y4Ca4m|1-?tvS zk-f`{Q2MW$2rM+Q{B}o!lLob-hlNn&JHt~bJa;3~vQkN-F`m!mUnvApl;#y{sQ`r3 z&7DuxdOroJbvvsml~`Fe9d3MUpBKSj_vzfWi_)Cls!(w&R9zKFzmkT8JG24K_n|RZ z5A$-iq(AfAF>rspM`Ac4vuH`jrU4B=TN7>KRr<_`6ora3r$l;9;xNud#Hrw7vHKQK z-yl{I$Wj@|RP%l_<2^m{tu+kIvWDi(Z_Fm8BFo*;(60E%uDca{g_Vm8{nI5jpi#aAPO6j5t~pmm$=<6yM`8)Jq9sIU8> zWOk^}7xg5IUY5|kfmUgPIS!mTp)~a2ieHAOs52G_QVtzB#t{ZGB@*)=1S3Iny;^C< zWU|ca+7f5Yz<@H2ce!87f7F;dD-wbLlLs265h}-Y?}(!T}*q&5-v*o%U}v%7w|ks_ZSE_ZCKWs)8)1 zNBNleK?>{i>Lc#u&_llK#yZ%&`Rj*3Euzh7}3h)k_1vti~>HV zl{O>W$#Yw|SJBL~Uy&~tdq8t9@B%ip98VfKq>cM|X^fYorUI;S`u2y4Mk2x18jLxB zFc1tjNC0)!0zBjjb^j+&SNB`%w+Tf?B1x%1pLucsTeGpoBxOTgZc)eh<|c zbHVO0PPWK860x{YmUS`cHJLhLf9DLMjH&)t#@o}FFEWkkBUYzJ%!SgX3V^vsl@$xi z<1L?qdLJpr?CfNTwoRT}`(~Fe zT~R@Z=(rq|)li+Y-mbh?0t{L<=Q?L{Hr6*^-(e3GrEclE#3Jjel9*vA2>F&Bt|4<0 zWJq8$`s{<955@_19iOfBZs4aO4htg*~P=a0cV90i#+^c0Ka~ zP?O7VQB~A(=M?kUMPE5<6BY}=e)**Qv2Kd>x%ICdDm&Ab^z=E6`n69ej8`OcYg_z5 z+SwCtzO~u6*3F}{^@D3#UYl3clgjPyMZwSVaiI)V&z-gORDOZ;cv;^}djUT!X3+6wi|?lOkQ-A@%AQ{eB6CTiL2 z2YPp98QJFEo3R(BswA7-lZTtbqj?ECQ#!)yZw5!H1<%guH>c7&Js+w3ny%~Zu4G`tcB zs(XHV-CZaZFmbUh2>@0j49&gSIVdhnysBUI@@g3O4JpGdb*m%(1(!U6uhW{BWLeqq; z)mV`2$(d=bYGu8!eE~C-{@KYKh~}Pk!?TusR)V>fzFfCV<9)wl{agoN)!3ib3*$Ul z=ff^Dt6sW_4B4)^dd~XZ1i!ZjZr2qcx~~7y0BQxxyKZ}n1mI=SkK-8a&;P4GmtX$k z7d-I~Vhq3N#2g&7a_hPv~^>N9nt^UuPI)8Zt`V|TOL|Ia&e!`ZvM6_ivE5p;)$}!t_b4k z&7VLNf@b@qBE3uba(9vFOL^isTEAObnmfS$y3wPBHW*4-_E}1EqkDvq&+BXe+oEJU zIxp@)#=y9{7dh3SR7AQfq8?^di#G}RqJ|C>fWdAxkLoS9XZIe4zj~rTX>RDvIni(3 z4blk+wD&I3MLe!ui9`bu6o19U{XX#U&>h@RW*3&;n$mzD9)4h6W*fWS1oy`S!}Ad+ z2P3GP24dWYXQ`erqnI8bG8KlYu&w*^e8A?x`~y>_PMvD>NK2GB+(5Q=(Le_vR&BvO zTw?;WW#3Gz3c!#8qw-s*#c(6r#yjAc*h`JIAd&fh3=O08IEGXC%em;0(9u{S3~7LY zE1(gV&#IGMT0`f2w<*QDyb7YDWfu_b|v)h6jxiDm6BIF(KDTGqEqSt)?XX6P-q-2OV>uc~ZC zU2ER2MM0Cco#(a5DVwt20^tZ*mwQbb;Q6rPy)NRZ0+T{RYVmZV{L8tUZCI4y;?3TB zsv5@XEU?BKD@vQ!HpXY=t5qs@-D&q@2M5;ARiPEhJ5+!tO)b`k*V8i~Wr#EtP7>`B&u6hqVBll||CP_-AbRDoD6gnug# z^uG0SOWs-9YWHg{M>R{#UaWQgTSU!b)|MBICtj(gqWc%QW)fh&*b6v)HakErp7DE+ za(mr>@Dhz@oP&vrHTIN1nYh27aFgiAO^L5aR=jxK6a`J?y-$9mt@ZkfwS)fXjxr>F zP=ogm4gmbI9;f(lKb}8-j=%Yz{HM}yuX^ckMYtV($*Bd}P;Ck7w_Nqj~$i*Zn{${uhDv)lOp5aU(LE?ueY z71CU{h*XTVv~RyYzrW~Fb9(1C%B%O&T)PO>%>S8VSfV{xocAoT{%l2NYB%FoMazcb z-ecAD!V54hyPUM<25HfnS`&<8SfQiV&;@!pP23a22^>$D(@N{PfvapIJ<95niunlc z_ane-4{gv)#y2C=^*knqmvNd?il=CL!aeJUZq#%+ETlkGny$c?;&-q|XrPJ;$so%_e4V8B1 z9)1W4m9&&3(skTb^W7pAUo0LgGCEK#{1+9s?ObJA-U>?O=D0wGv z(BrBr0x5!a!I#zY9tA$Ql1RKw_Jttm!dVZY>immHDN33w_rd8r%B>6+sK$PoE!j3H zWn4?2F2?_&I$p@*vSkz*x1G}B4JBX5J?r*8=VO)C6-=n$ZKMj_nG_}5Y&8+fTljUE zQ$pjsXq)b>-rw0h#Qa=KnAP)(!LQ{zD(5TZxnAj%Yd>H78Np)FVr+dokKe9|dCp2{Zs28hX`a2XwO`@t7hgk~%Miu~oIIIyb70R@SVILgE7sZcI>-aSc`#ss@#-PE`-- zGa0VlhWxy1Uz{^GKF%fsLf)=gD`KmTbxHP72!)*AlvS!Fd?(&hm-Cr4Ih<-$I%>In z>9Ossl!}sVy>fV-rG{Lb@#VJT+AA05$$3rHrza?c=!cL~0z<-D&Y1i+0r8zQ7f&#R z2Aqozt>LXEi&N^KqT>(x(tq#}04DHWV+{YOj+2IGV&U%6OUfyc5~}C}X9NEflM~? zudJetbF(jogFg__P#c=I&auUp8XW2$Qrh9ZDW1{eig;8b)s`n$hN#P*weVV2l_=}j%$ z_4JL*EbdG=XMdB);HCbe1S2aQ02(p&)7}kGwf+SH43Gx%&U^pPX`v6KvLDBYIW10? zA1pNpRzAq~1_jpF64?+o)eL}XRIVKeIF7>%dLOSjCRR!7v#tOOWSc~}tzZd6!V;hM zs+JC-WqC%9A!w{13>pSh;U= znFbIDOO`e<rs=S72l`q`3gjsI_c!3h7+Kz1)Q43zQq$3!!Q%4 zRp74}^&oSh?D?X*cv(6i@<)$MMHO0li zRn1!rRs8M0_$I#hy&qh4#2=+m_>ar+2L}NDX**tdoQR< zoX&&MX4}OlW#w8C-Mtg~lr*m`*v`9JrB_}L-w(<~x_qwX2~*eH!GYzamR=D2-qW^? zu?nl}{t2q&@}6?>*Tu{F{q^D}jmBubm4@OFqP#xHejYPDu+e&*p$wni#}!Z-Z&CUT zR2a=LRocm>cP%|kLo#PUgm zr=B$0Vo0W9FFL2;DW$@LDoa4R+2LjUjMd_bHeGH=1!m5XYze8!0cUz6Bn_J|4#WG9 z8Qjk@VqJd$PCl{nrO4ZA7=>b0l_&pWfobL9v8OL2jLTwart!90lyV^-w+{j6u2@5P zAK5_Ha<0g7-lAAuK9q)U0Uspa$-+UdWo&{IxrU|ef#>jv^Z3-Hgo`X+3uqhhFzt*cxyB0MuVRXM zjaSe3?YV}$#0VtoYSR)xxR9VKJ{1a~YRafAE`@X#50MPPfaB*7~F5lZyJs$g8tPlzo(GPEw|n?PAFd zx+Z~>_&Wdru8CMKWs}%J(z0|~8M|Wh`EOX(&(dvAIRDmf^P%zoL{w1c0ZO~sy{TKV z#&SM{Y!9v(uETw7_cpG^@%8?3@xFNSHv5NcpFVYA1t3-##J?~5vy|Pw(&;bdsE?DD zzFnlX_wzE(F8Z#H9T_z5y!)zr_nmL!?F+C>$UDZrn#ceZu~$j zrqd19OvI$nNqY65?PewID1d2rE0~-P54W2cjOI}l_e5)Jg_I&@+;iZ1H_tVV|HtQ# zfLQFejx^H7Q1p)Qv@#)ExH{DoSSozeD3|nn>(KD_jbq}t zAAsjENRJg@AvdJr8+B0ts7QL%W|S%vozp@H=;e3CeDb=fXg5m?2(eoCI;I&o{s5z+ zn>TWnvEyfj0XMz1q;yJ2%Ga8KE9^Haz5;irNdb85>th&;p!E(NiaAv*Uv!?Y3bbxH z52TrSbsUU0&vg4Szg(kl;yDZklA++r`gERW>s*+L(cCz! zo_aI-VHF#o=&f^Bt1(P&czE{A&uw8Jbb5e=>oMm-S{7WdX(TJ>rk@-hVDFigX1~Uk zR=K`@w<*Fvi6ssp75^^x*_7r+PA1r{REDbxwkXMA36>KfPK#aT@}6>FF}a1xbv3LP z6TbXec?x`9wr3}RMWVYV>)mMCEKksNOqO$Z>0ikB1q4V&N;Ck~FMjglMtAPu#R@WH zNQrt^#aT+>vx5iGJW08h$~Og;pA%(JGmIfU$_f>eL87H>n!;qM=R*OI^mnL*GN=Yn zM%pkL(j(@J?_cbjig{_y6-`cj@%*~jqRa^S!ak_EZE83H+ggr#y($d3Ok+OQyxRBb zYy<26S!{6oTpDXL%HKT;hRWK8sxN8k6$Tu@04vRXqmB`00wq!;5M+psk3;Q~GPitW zSk*z6a*xZLhD1IW_1VJEB6b(2+F}%k=U(E5Z0DvQCEr0DM(_n5ax3wK=m+7ILa+%T zhq4)Y;Szc!1$Yp`5EAXe!CkZM*4CcYo(R z_gU!n@_2&&vVL277Cbhjx@cRX}ZZWk9ANcoo+d?#D(NF z9Z)%r!{Y=%pbps{`RAbl9Py5+7LO(n!20TdeBz)7fB`pM9}g$BbDrV}8zTFF$fmi5 zJAw=A=L{h}=Br#BJXj!1nH@;MKX1=G-2$RyycLnNri>1bTIwzGjc(D6`r~E12fmvO$U(87&@% z14T339LUne0#q7cI-)G1tc2Q?6-(QzxkD)^XO1a7pG*|9VSVpkwYvSoonX(v!A{eZ zK7e^6^NUS5?Hc>uhR~;Lfg>-(_iC$9otJAv3t~-l@Ie|)6X|`p^yjuMTRB_JX;}a< zh05#NdyEe?gsO$DL7DNsK2cu|186AG+j+&~ z$MHyN1bGez?Hyq>K6*+2V*w^D9nc0K=-JsEpJikPVRe3Zj=r9aB;6wLoM4Jq&MTT! zoST$Ig9-`2H@ppm;Gl<^h{tuE0?=Wr6Yv!qHz-*e{3TNSq=l6{O06J~h$SM`achmS z)_8U!*!O(Pma)$@t-%8Gm?mRqUTeKx_Ko*#=nwZHmT|EB%bzxu0t!~TEOE&Ts$t`7@<&wRbT zy~*GF8-G{BL&|fvq#Q$re+@`ebo41fN;PKR{ovZnQdcJVAP|)Q;W%{E*B!7 zeIq2b%2`wf57^CkYN6fbH@G%GsxY?AJFCWx3W@MAU;_O>+%NXf)tAz9gPsi@=GnfQYe7CK-S>QsSK}rwV%x;0YLV zpE6EW=g&jnhPDZr0c;^aRtY&A&q0QWO&?Qt#aN~JJdSFwRVuz2+H^4Wq8_mC80EWv z97me|BhnV)2B>S{bIE65y=2~3x=uteC&~1Nqp;&6f#SoF0^LJdRlFxf$`#bPV+rf3<$6EzworMJ(IeUrm^o(pF&XF(ee!?BXA; zVHQi^Ak!+-qDv*??QEY1d&QgR8rsmA;?C?Em3bM{MicQXXrgA{6&in|b-y!T+~y3z zh4zv*JjoM*4!I|YJ`?fhFn&o#??4X{T(QRvRVgv=+E%0Ys{2LdHO~@hrBqYPy&?Jb zlmsQ$!9@n8lemJeI)KJwFA|9+g8C|gWm`3Z+sE+^avse*FxSCCs}!@I!nu}Eg1>(vATLl*1M;SXS8rD@zw>^6P%M!NKhc8;M{>jUrxV7X52=IMf9~J`F;HQQh|-}fMf}rDPa(w6AEVI#?zZF+Nh|Ei)O1%6PbjeXc1oRMzzIdK`zCNBs{!Q1>f`(6r|A??ckh zUDT}wYCa4?YYM?Lf)%>fdo^rp&58%$U_i{}GF^Z-#-N~pBenGqj?x`KB~9a92#*wR z0VY6zT~+|gS`NHO9=aQNrx`RU6KafOcrw0G43&swP{Wa6f{(#6%Mc|wKIWi<12vJE zz%4gyjG{lv8$QpE5b-)JO2T5|4pgme z%6p860ZiVjWV+41)ZHUf8l5JA{srCf`4~xE5D>8_5wMe9Vy3`@klI`l`sX2C%jWf0vT6YqJ1n7Epo(ncwKIRo6el6Cj0LQ*Zp{2Px6u`Ns-|O{Z0q{LsW<*)G z>v$aUhvX;Z@f_}hl!&pv0#SsHMl!pTb~>mKk&DwR5$bpb4M_w)K5Hwkv{@#v30on@ zl8lSBoTk@qI|FP)P>RV&AcBzPdhk6Ozy#86e@m0`{l4g>XeHvvLiqm7jV(YiPTBme zA6YF7k+wbF^CAyioRr)p8JdYIEAaIJ5 z|2YEXX)wozobTCBkJc_r0#+QBc5HwFr!kpdGG#wi1^czi3 z!w+}_H&juNV~_%*3w)2qBQ*!kum*!MV!bMYQw1QfOvg}pK9{I0HIK{SZiwbPY8*!j zt6-8{EAFOjLr3gqk4fO5O02s8Az)^k(U!w<0MY$@+*E9Na6mT@v7D!QP6tnpL(X~n z7!J4}EZTIUm~U7<9FId(+-d_>78&C};gK4F4%-4=sZkWm6kY0Z2Hk((%mE=^DMwfl z#P~$3Y(%U962=nz2pX7v&tkHMWTpq$PxM*hqB&C4DSzDY}Mm*}I=((?L;u93pIQKo(|w zw#XM4?X9|$v$Qy^G3$@wOa>q-<)gnC^r&OSr06piS@PUuVqi91*IOpLxz}Rr7I?sY zpf0l6bTa=Lr!#tB4lKPxJr0kQVbq&3dSF2Dw%fq{L8boY4{nco>@y|1J21 z)k$^R0i$Yjn7kU+z%?>#qB(*8INNN^u1su^vSHpKsI- zzgD3ORwULe^AK|PBxrt=IkYA8thN|pH83@}8UGi}yz;*HJTa4y_MFu^DqfNSyC%zH zqb&r`Bv9NW=a$@n*dD^cm^UJ45#tc_z3 zgaq_lGjF_EWfVZbAQ4Rs`C1FfbY(tgSObuccvQL~zJQqfzpvD&LK7Z7>9S-_@+l>O z2SCCy^B{f6layLvg|K|2HA9)+&pBlrl2#033|X?u-C&evUe=QNoUXa|gai=mO%QA_ zK!FjQ6@nCk0UeS0PE||~)#co|sUlyY{}(c&>2TZM<-)hE30nXY9Tg>8R1y?$3rqyy z3W02hOq*f8@A^c4cZr0FKdcA9IJx%RQw^>hp9q5JDGt@l&<8PFHka3dKn%7j5Eudq z*1;cZAn~g@>D`xH#JyI`%z9c#CB>A*+8-KP{MGkY#Q>fdU1_* z*3_85!UX=t9H{FesJCgNa`OIgca+w@8uvc)7)=H>GNu>MTN70wn~NNWetwj|lxK0! zhR*!=z;vK#@I_B_e&nOY6AmsQ0iw>YiSl}>4P!CDJGi%Bb&jwF-1d&`@1T#I5rP0i zxp_%m{6rP(oGEwnRf#PvcW$2Rlk9%OPuR`VhKh}!D3Gcoir6pqq9j`H1=7X61P5## zHr8MKKm1?hFZ~DqfqYz__xgZazQgN1&HsKq7RaXhE?%Z}Is!Is@TrJMqVpLKItXbY z&XNRI1xRy&2p*N2TcT}CUs1(=?T4Tcv)j?`Lgj1klUM(Rk=+kjhVMyi4;aou)PCm&##t3vm~*Udykn-3`%!4UY)Ocf|1hijTM z_?np53?|%G3fwM;`!fImU_Re`zMy zpQB817^RL#S7>7C(&3p1v+<&$ay*8JM&L*j(POx~2njR*LQ?Pq(;b!oV`v2|9BL5K zi7XXUPco)KC;{pSy15JRdh-QB>m9jq07WP{h=9znXQvUZ4zGBA{EP$ul$ z?xZ~QQwJGHxNMaqz?B6Ry+%1VM;lugP)Y5#{V$iREGEg;AI^l(rXbf7#-|&h(K$rG zg$o3AIeC{2zH8xIFu?#EDwv>#3{f#Q5bq>ilP(g9 z5AZOKx-SGaxR46AtJyuaqdPgFoK0)y_ht5{u^8X_eYu{_y{{nt1YPp$xKRm#x(T=Cv6p6Qn*gsw zKp}K=va_Y{+swN7Ve%nvNM5%f9a&>OAR?Joijc?~mhn0at({#!?!UkKg}AaL@^)K? zw)=NeXUWK11}7-=#n{idM2e7nH`jbRk!uIy6bv0M=$h2TMmfodtuW`aa0Nh$xL`7E zRUY_hvko1l!xQJ&(1?^zfu2IE?798?RtCo_{v`ay{ScU^Yl`$3qKLYTba!!1xDWXu z)25tH4+01cb_#;A)>beRbDq+6gH_A2j1c(eIYp1s zu!*=i{dgR*W>^)R-qa(^|5q?B>ohU72sRoS2H@H;M(#eC#b=79(4sB`N?fJ=!-`*X z`I=H~K?0F`96My5COW1d0=K7S^!N>G%fX|xxKQsa;us!7)^reL93!{EaJr*r%S?}O zqrgfY$>955F_8@PWwh5iV|RC{5FI3I0jzs`&fdol*Qn4N`B zFCY3MEv(^3eaVb*3z~U9gf#+@>(>U1YHMfFzS=5Vn_Hix{Z{T&)h-Ab<(9;wNA(h2 zFMuG%IO$T24a+9zSDi9lFsU>2GKHxjw#&_a4cS5H{%EqwB;?y(2fc=`tO(GMiZ*ti z&Rdw^LpsEdUfSaI06|A3-J~F_mOsCd^$-3bHi?x+6~RNr>iraCdw(HVuH+N6`hZSb z?b{Gf!i^x>lb}a&1#G>r#Ijy2FpVEqfP;R9#&2rj#(;CE6fQ%NeFp7Z!YhQW`yCG^ z4k4vwL|(@r;B6#p98!E9=Ef?}FZFfb9!o5;4FH&_HD!w|Ftc28i892E?P8}yza=#C z%zJgp5Wx@87Mo#lc;u5sH)~9#_aw$~bCaS`_C2mC?5co-D3t}^Kx;zikEN2K1aZxQ zd3C82NZEl!b_ckihFLhuxWT@-e;2~>ErtJ^!q{bIA7j0gZkM7ofPsn|5^wG_FY2jv zQL+VF1DncP`dEMRm;a1>{d<2)KCaJueYgO8w-@h%ci}7@CNk7dEl?~N1;F*+A$>x) z<-T}jV!>EKZ8)6uIE42gR>Nq0sw)abiT@wRbZ_G)Hqgx&^j~|S?82Fr$?k8 zZj~WYK#W^g__}a`yetnyB!Vtlf+fhM4fl!1;}P}OTv>n+-j`Vic2Io8eJBo06LbI& zgm<9z@N5FHs&h`T_LlpLs)?y0)|03A1IdsAFu{lbOyB@<97qxj9b!bF4M)V2NMKohd()xF&>xy}vP$=@)h z2B5pfs|){i29)4cf6V|D8W z&;VZ2z7P72s6O#Z*4&Q}%mYZGj6ubg8SRDYQ@1!G7X<-*-CTg!rGS80xC6Yrf;ql| zeA*`FVi}fw-GB)u4+2+6yMI9XHZAb=)zxYGx4UXSdj^r$<>$gMqE3XF0oW)P0PcuT z9qg`SaVA;d*(z$fblUp!|F_jLSAATc=lZY!_>QhEzcYZZmzV1)yG7Imb^}+kmtMCC zW2-6|(Y>}PK3FeD+S{#Xaz`!p>~{C`L;=MXUuQsL-yficWN#y&FPzaU3pW&MDI(e!!|RA7jW;7yQrjOrhIs$$@vH9Jma8`r?y_co0B6 zG+joH$0L|9*Yx5bMY~m@5%%^yJ{`f>EV0l=fLVZ+$=j&8tkaJ^>RfXQg25p`qJtfW zM8qUL9>eAgBkVk_d^+Y_R@y@d{g(5St`ant=SiCTICvk2LTM7fil2CI=P4N{JT%_R zT&r(OMVICH@xcm!V@UpoJ*tE+oY7;Wpy#2P$fo>(?zMh&h7!KkXvoJuEYor{5`ZXlpVf zzFR zwAH9ASRfQX=kql{Ga;--2XAEc5~#HU)_V+?0-xwjh0s#{=cbU);?xy*P9m4(+jTT!PD=xgy|o0<{qrQI z_y$5}V3>?i9o$|YDGN|FjmwiK3TuEgnI?aIp@!1AYtqxW;W?#ZlmB2~pfMpKTwTD) z`e5}~0s_z_VR&7{*PKlrTNIRhBGyO(zL-DFwa1OHAnhyL41as{D<$OP+OkN)h|nA8tj!4Ltccg#z$`YB*H|xA5R;C zo-Y^E=Sb=Zzbon-gpK89&*z#UfVx+-8+2PTTZ61JMC$L*A&E`k@PFg=4= zGm{B({?y(CBWx@C7mfxAKw!P3wE?#)=6apey$sZW8CY$ogi8V#Anf0TSOYHnAEPwE zV;s?6Une?{0m3pX2FF@h=8?2iy&S}dU~Oqx3NaN5O-*A^S>IbBwj=pB zwRjE06zR|LvrKDSn+J^-Yv=c}!H|c55CJGv3M_(ELTvpSTcYEgoa^beV;{dWtAWgtEt?_-F9VRe1gvM z*?<^O2~8x>fx1N6CLnMPKwr|qXaKC_YQMuu zvc6Zd@LUM75!g0ioCFm8azi+k1Nl<91BiG*NP;F`QuECz3jMT~`K@on+5$YZPlE_X6$1 zuq@dzHeg$KyN&0fNc;sxpmE&5iuPD{o#px(fERm|FrRO1-@S4_M@(MPm)94A68D_< z^%c$Ypg_6;((t{xku_ARvWl=(YFKU5DloBmn*Hiu`OB#g`M5sc^#K5U{>yjE6iJh% zVnmB0B2saExrm2$+LIgh5|K2COPTmeejjhF?6pvBBL z7#FOId}c{9=0<4)B?LpH2x7mj6x zjtaQ~ux1@Bud18=V`PWsZ*#_R6v5}#TAtQ!X#PWgJcXa6-+qMlmwB!VR46KQPS=5i zwL`8M6Ng^R$8n4ypFTYV=BM6H%iw;{3^4Po(B8w09t@vZ^kvDMXPV9GaQA@c+tYVP z27B~HQ|7NUo+|R`ctm9V(Aps8fC2=VDcHN)1oj4EJHS<8MY|*z$Fjtj0TLa@2%k}r zGlEIx+CGx8HP58xz*U;>t|=;!O=3;F7Mh1qU}TYtFyUVXXsrSwQs`C9;DpKQVZ0K_ z>jb2%JaCV*xbO5zdKnw#R;skhU}3L>X#YM+>qTt7Lkc4_^8Ra@**8H|$$h2+nciLN zf&Ov`$PGaX#7yn-PNztY&+!;cB{$A|14Mgy*YgAOwHep?v*YH}X~*YQrBokEP>b&b zJjkUy`f7Vi_(Phtg$M;{bPyx`;*7)@bSb+X4kDCuAlYY}LxaGeWV}oGO9598G?IkL zmHzf4O*`<5heRE_Kvp%7K{-qJtAGa~8lw>AVfqdb1C`~{ zrm;~LWTQ^d0vuqmCxzkVE{=UQ+-@UHv z{MH7u{uj6RYy(?Ude^r;ZPP=5W{@kjx353_qj%=x`p3RLTmZgx?{Wy_qB*e3bMD8Fb>kOgQ8PhXB zf?!P73Srwzi4^j3jNzUDvSh8w&}HK0^yhg-yaCT%0CRed1pzArM5RVngb^m~eVn_7 zfYU6B5EB`~D*zQ_1rRA8e#BB3Lo)!DTBUh39qEsrW|ZPYX2iOf(*rt=W2BF(3fxUb zt^)*w%KaJlF*nF6paU)u^`BH)FjY4THE)0To<^5F)nHjuV6Q@vl4G+h14RxkL3NA> z2e_uZJ)iQ0syvQIuD2t~DT2luoAL0_RwQ{KVR+Nl@}4cejDt>Ts|o?9l0Q_KRYp01 zaIs|*YtrkvayP&;z)9HrpFs$Sk({U6T0f*rG+%I-XnQT;eNT6$Sz8;xw%2%BEJ$*u z$cHftO#?45BfTC94RjLzjJmp2hX$CpEr$M;2)o7^ig&ox4H%|$WmN^a@(5W|loUi~ z2ahR)K5CQ!{Hj|Tpb49N^H~ErE`s2F5z_;==}^TPV^m*GR%*$;ZD~oMac(Y6P%edC z<@U$-RRdhX0BKEY`PgHms}CYAI9(G03z%SfkA7E3y?A_-Ry%vo^S3I(`wmnzaDnEn zzLOjZnpl}%y8uu4iUyFXurLdLNOJu#V1bue&=29;qPMoK@#K#v>jR=o?9VnJL()RD zfQ&k?(p>_3l$MY3{7aJ6TC2Mh-flu_N91`XjzzP&5KsGv0*f}-17`l&4o&*pfk|@j z0uMxVp?FMpWvX={#nbB6ty|W)&O&SZk?8Xa(BQ7D*C@dr?*Mm*KIXJ)`zs=45y}+1 z(Jm+=ssjmJCe8Pu8^@3@K7H{m`}A>rm)C~{z~{eIMC8*CRD=yl5G8ayA5yG zzYGS@uU-bU!Pa+oYZfEnB`EYquUO}IGbAt*Dk|B);&fRF0_rq1@-xyp((Ne8e(0aE=!@47&>Ig$a#fOWJ}~2VcWk%M=U&xdCc^Op=k6N z7qc-MM}j|LeZW=}Qi6t?cWVMi9&=&HV;oYfx)oChIz!BB1im0)x)|bGo5}9czE=VU z3J}1~M0sUpRDV_2rxLp?K9g0zMK&d&o-v7}Asgtx!hV7Rwv~lft5;NRcWg?t{fo>@ zKuNj`p(VvG=It1t(VnZ143uzNTd9h1?1u)Ja&4{i|6enZcj4=5;;>a5H4YSlq9d6I zKgZ_{2VG=gavw!*TmO9r=QaC?J(6rZNg-uPRes$o1xqg%n)n1YA@rv3U(a~C8Zq50 z1Lvhyl>+`n6PlG1y9%U0z3My?BbNe$(9C^+U=Nc2S8fsYo+dQf;&Kz zh0klz{s%uLDb!xp-yw$fG3Q|+s-k8d1sP^GO3vJm73kP>qu(dL19wv#?{@FA!wFrdgfdEZb8IWuE!iRw7@2nhWn5F|j zSWD#jJagsx?81K@q5#QoxcDzOaE=++)9ZSZ8Sy%ppV% zqFw!OM)-$Dm7910azW$0fN?{#FWP#o)-Wod^gjjx{6i7;b~-h6G|}R`Ayod@zW4Y7 zJU-iz)aIwO`#hW4e{1uBF6>AFp0`#%{*+b4ei*`J+ag2kc4lA9lXhXCK9BfIZAr_Nz0l18@ z22hKoVy%A`YYSo}&MY%3UnsyIUb1=z)#hh%i4v0pDrk5w1H4cO&bZWc)A(EQ3N7d^ zKocGxF=;Uha#3Ezua#>E>xVjO)n)()zKkptuARZCQAxT%%*zdn<k)`VxNI#FisfYGrte6+Z@oZa3>?bJZLin6#*T5w3Vrmt3R{%&@ zs7FB9pU=~M^W!&-;Ce{)XXZll@i;;lbfJ4KOJO}3vS`6t9@q|n^fWa7ZpdP3-tUx~3qY*$D4*v{b3kZ*+>E`_Pq{{B$(m&m02hPfa0L0Bl_Jl5$)WvPDbONLf{$JH z-Mgh?YfhWz8G-#==ynSRjU3X!tgZ4p&x1FF&^af$3Y5<|tQ3-wIb3LVfw<=0Ed>fN z@l}l=fwd+9u&X(^aGs}3n}Vp=Wp>F+0~1(<0LW-OC$7-=JdR2m*|cZ@7>I(&D?uT( zcn~tu%1P3aAT1!I1YUp<9yg-0It<=C!8!VP_*zk=z=g_tIJEl!L|BqZrn}f4(!-}f zQ}lxbFf z0ZB9eweJ_NFN1^vBsa%&QvlT}4}wGjk91&fv2Ws?TvDWAcS@OOYgzUXcO=w$JCEKXw0SMdzo zgm8UTr%Snt^q;zqe4UpZ!y44&kZb%-|lm~RwQ>F8m0+W7$AgJ!wKF>1We|tW23g#Jt7g^HC7`bxiNGC$sEUf-o;VxNx`SIvx-5{bH6a>>i@C9_t{02QRbD*8T# zy2T74^3$4dmzB{hysE7zvwri)|DDbcqLmpOPl&ED>*b$hzlju6K{ZAIf5>$PYn(t0ZB3*1THER@;`0v{Klz(+ zPph1~7@g&;{_S%{;-7g+8C01G0N z;*~a3Vv&SusyJO@R`{-sb4U?QH;1R@%|;=(?J&pg}7ryhO=L{E*ReeVKP!!gEaR zP`SYeHo?2@uUu1kdNmgJHNTvE7FJ;j+{FKEb)%ihi{0{@)o8YiU^j;R*0e=wLsMhT z00HXXFd810d_Zr&yccZ*O|vXxE17}O?cE;3pguEzmNepKJoQSz+9)Y^cALHAHv}Sy zU7l4ji*2FDIOIn^^@nap;oHhpAJ^~i`TzjFzw3VGYNR6idMX4HV`@Z~RUxl-+xB!3 zvkkJV!z77&hkjcy5Bi>^Z^d32n7vxP)OZ|2?vwKm@@8iR>i zC=Z~;P58s zZvm>5@Lp&>;uYjphSfz_HXxWDWJP3jC=*N*eRyF$Mw39OT6<#*(W3wa#7qFdl3K=C zU+^Qf>5TmticFED>Gg>DrB{Q@&Y!W^MDEOSGm5)#!4;olXRFL%v71l%s1mH^Vnw13`3;`aXK z1iiXj>~Gy&FN`SyGwSW*aeG-F`uh%Vs<(H>)zGaNCMH6xfiZ;L<00BhXWfAuyBE6#Sd0Ihy%7uaFV51+`XT3fEfbaI=o!2Gfe8QX> z{_x9?Lt?`fRT3gabqrS28Ig@pIqmMpg;JCmk_THe5h=g>rPjk;^Rpr7Mg&c{^fI|C z0zo-jn{Y>iWzr9%Y{T6E7aD3LIf8DoR;Ow08IYi>TVj%LnFtYnYfL-q0KSQI^{MHk zwKV(mGojrX{47l2V{X+P91WSqUs#4Tf1yD#%V_=!8u#fwm}{-nl;f~-ZMUW1>pt{L zhK|teyB79&o*5lJgY%`ysL6aT6%joihfG_#%*c+x{oV@%5gCWd`E)@zQ&EWoBWaSn z8#9H$yr1XHpm!4~^;WH62&Gs98Dvu|f&)Srb?vx__i>)?yRRzYwqPmnW&pnkUYM2% z;SK@Tx_K?7jxgCd(fzg^;tMo92 zwW!_)8RCL>BqPE6CI3+XW$oRyL`G=rMcmqUfg!NAX7hKma1j_uL&Rzyxf_QEBb;Hg zIu1?1!Pl>OOa(CY(QgD^RE$4BkG9<^=vwxHI9R4-edFR!2}WVl6tZ*|?wMKY`=8-K z=Z3rH9O>fxE9Xov>SN^QYXu4rjVapI1#56= zW!VsbLSx>mGt&Rt`=s^UCNMS4J|wE`z0%hAcbTHU2LUkcrY>wFOJmG$QvdFO4u2!h zTI}9zq-7b%0dY4q%J_W5>Fo`cgge~7Jg>|$6X#nX|Gy)$R zKu`zWO#VS@ZHGp_k_Hyv!R|w#8!7yokbs9e#rs2Nb-Ff)&^u+x0AlAf&@Qw=a~y+2 zipBB@z(FeIswb3j$#X5!lqoT+QU4+V5#d?VYJvnpr;!DQqCcyx+Grck^(MvRxlD!< ze38+7w|%|s4R)o#c!QVB3=0?SG^NCpvUV|nfSf~^^TD!x4E_ZOXUbcy6Mf$Hpp!)x zm`T4d$;7)d=iGVbHmF3?yJbM_WEXZf23YEyCVn?&m>-WZv{w&calczg{@JZ$@32kX zieh=i!sjU2>%^pp51=E3EJ%{UQ~d}6_y=))007_r)wsiPJme4mE1=>AapnpD3H=iF8riT_$^nKjU z^GyHt(guh;&oh{;03ycl%mbz^FrE(0fQBOBYyWm;@GbUpt4Jw~<(fQyN{W_Z>JO(7op%TT{c%NXH- z2qDmiFoJa-Ru3#4DhNt!s9T0BH_R1zyqXXb2*^TlGRa7s59>ck#u~epkw@{+8bT*$0D)yAjeaj5&b| z%>?#6(Y}AX=^6E3?tj<*ub<4MwY};79=pb*%y}_i33_auPuHhdl{gcO%!o|_VA6=m zBHM|oBF!Cu(0V?{Ex=T^I`VdodKQ3I&vqO_K+T7fpmO|3ID+IP3#ryg(<}X@=K920 z%2skXZH%3iVb#Q1et__q8&Hd|OYIxS2sF;SzHH2_BuDZi$@Rw)FaPTwYb8MNXIYZy zdV{kXgVjfhV7wQrzSztJeYC%2t)yOZ^^XMjYvuJpMNDCn!WNO(KDF0s+^+Pclt)%` zwh8a|mtEHN{ZsdMURgNpHyfDOIexjWW=3poRzva_uWR1pWnby@s|NRWDE+?*(xl@C zgRt$s z{9uJx5NCmdM1@`{vZuQ*4QQ;tDYZr#dfkBpF84ZC{p`0m5T&)R z%#dB}Z=o>_Cd$}vcc?=zso5SH>DFfX{6fnPp=P*A`Oq+HM-=)nGq1pYtR-lCrqD~`i)if};KCg3H2>eT)&(QWCwIMy2UszL^$aoAnpEFV!LQuEjd%!#n zH&dT;di|02T1P~~j#X{bcej~{9O?lFBc0!}6}gq&+9jsd1#5HgU>pu8%(*1Z4W$_K z%-SH9d@R6{0yRu@tlXmx1WEe5dYVI)YgBA$8Z==oW6fFw6+|qF)7<=Dts>s1&v|5e zNV-Hq?ud*Q?yfO1IHHn2eA{SE2(;pb*+L;kzJiz)eaW3`CbH8wlWT74+^3^ao@U+; z8Vuo$if7@!MeT3>fDlXD7gTU-qlS5SbUM~SSrk}OLKLfVr_Jg*cj|M!?Q1ssofNiS z<9{eC_Jt)1Mo=^ZoYtb|gbGSjS8Z<}`EdGWbr*SdXut+=`+#na}e0(Y_=wMMWy*b`4lsaV@LpHljo}j5Q=(1N70IfObt>N5kmtE!6+I9&6lzkh%hb9 z`VK^$g|?wiuGk_iK}*ZAvKT9s2raP|ek-szKhv6qTUK`6&6Z-^nYV@bW)mi^vS>OVx9=_6zQ3iih`?h>o;&NN`KHI=6a>a!Rjxqe1!X9Wam! zPQa(4&8h5TVI5$(h6Hg3{A|Q==kK!>+r6Tbu^wa5>8tWZ%3MbxHkkQA`NJPB`RSke zy5S-p{{P?q^U6eB$tSO#D{1OU);X!A*UZ@5OcU2Yx4SIV{H0s@GZwD1P{y_h|7;lbS&;qDxwcl?I;@7KRjak0|89Mc) zktx}x&%hNE=n^`Q>oyv@d)IXiwf%jm#A@@l0V1%T&^{DxI&$o2#ybK;Rxf>xP;V{; zCx~c90YSZGe1kzGJSJ z$7D$m#H{vu?wb;9TU@F=?SU|LtbX%27!9&wZAPC8$Y4l{GfP>Xdo_OybnIOC#xv3^ zaEptMG{}ISDB9Yow`|{Da{~zVIep^1S&wPDY0DQ+6g8){$E~(3J|Mpk*8~5Z8j-1L@BXC{!PK1B?A|yYKJ~&%HN;RiMN$VH~m^OprHH1&PE|!lb!# z!4Nt}q~}8+iMp%GG|5zci?|v{#};aYSERbnD1{NW;U^hiU^JP!x7;9(Ikr?`3k zr$f%SQ|5Dd#Q%H-0=K_+K2I}=v`?yPk=S{R;b{%cGGh4S;TqixFsES}zn}&717w7T zGX=vKr+H4-oF5V?2USj_CM5qfTav=&lHtJ)^Atn`{uTB%5GIS^^FPiQj^l7wg6B-Z z3N8glq|3|TRVMBRIb2t~)@qCy2T~M~jIki1@^~C#;p&h=CU{KRw?_mnY=S|6I}76w z!aUj-Y9a!kLhu}3xLXy3bp|c-;+HJb`GNASwLAueif1Z7ia?G!AAl6rdA)5SY@aDAAjE zix|ut%h;#M{H8fKv5~p;H%kC1u!=2rBxrB5RA|B}%HTkJJ^foTyj?KP*Dr$i{@g|c zao#BZhH`GihIXICWAtMnQu>Yx+ygPY`9B0$l#aEF$ClOon!(pqFBZ^L zg{1j*)49|cgZ&H(9&QXD8F|iyEx5Hud9e=Kr$jH5%7~9CEaRp@XmAUHv=AU+mzN!Y zf#e))WP=yQU&wm21W@e7ij+?N``mtCF2F6&qf#W8F71AR2KnW1+Bdd#Ui;YpX z1P^>&ssaqAxddMbKy9NFJB_XC;WTGOcn*OE#*Kf*y{ZIes zHMaC|eJ|IC1;A&%%!d5Ve|Cx-PZ5E8o;9>s_)JfVUOc^sNJaorc!g~YOx714$h_w& zz0&g+r1ky+!)mv0*j5idYP2-W#J{PMR-)b##=VHk6HKgxBg(%j;TtM=cTnnuZ!C3t zERTnz^AYsNwW)f}X9PiW3cMuD!-eySTb@m54{{YF;V(2UA`$`GRtmTS`R1n!w<469 zZ&k{`^Lfe)f!vT*Zk5K#Rv2QsW?GlIX2h21JYB1w=?8GV0fr!len3s6{!8ErzUeUGjjd&tKIVSk#}`2bSHu-CTe8kG1qUB%O^v?$ z>t|}l9KW)jSZUqhOlkEo`>P%paLy^`d8QS_alk~@stj?ach0gY=0J!duKCRrfadWM zmX<O#otx+$fAdDM`)BF%CJ#2vBI{ys!8?j#@Wp3xJ_a2?=h*B-W78Si6f0fQ2Ms z1H7Nc+AOyLXqg$xLy$nsdkWG?N*57GvJkk?5t5d+jjfH8k~C&?pBckVST7+P%;8SK zZoZR+0fPwF_d6ib%NY62Ezx|X#%}%IAl(ExgtS99eM+>0+OV}Sf$yz9?~V(#rS~>K zCOT5DWxo>s@5Z4T%fe!B!~WVGZ`=F6gC==hq|Ca)O5gj;aX9cl&Vf4& z3jdhbMW6lt3W>TR>!VL~=3AjaB9e)y8jz&c^Fox{5cjUe6|0DoS2tHQ+*RT|vLx_h z#1L7vd*2WMd0Hu?H)AckQf>GWDD1X?(gF1RO#CLg*2H z-~RDN?Vk52C?25k%tjlCfL zfTx@`Q$18Xka0!MA0%L|@S7Lb2uUzy5TwIk2D)G~lX*^2RX=ysGYYJ^d{r#U=t@%j(((GC0lh7Kj|QxbJkxQsghACu&uH-Gc*in!+3x5^z%xlUjq7U8NeH4;cA6~vE!0JA6fpZdjL(O>=5 z|FBQy$MwBm9{|ATyJ~+OrZ&D9Vq|sQ_NsQO(%u$k?us05ZcF((`9xIg{e1dAmCG;OmR#CVj75eT!8#NedtRwy+Y%^=9GD!g@KU< z9Sq1iPcd+*gq^wYyb7EVtsq^iG~q7bu4GEZq%`SP?LuU3iL%$4!kh`lF)_m?@6>|> z865;bfn@-`TI$5&fxj3rGi$Z7ngAC}%$y!`pfWR1ObN`9Nv`E4oML};*)T!Pxu&S8 z96Ay>C`Qo7bA-95F+tJrjn)$0tInFcoh-B8w#pqLW`VUH*kyhO33eg3%+;?X=gNeZ za6X8+uWst~Jzfi!bB1Dla z4=vvUHg4|$d`b4`<#CoLncSUA-36}L?AkR~;ppzDZSfykkr1>ySTC@4AwjStWDD!D z($1Fr-Rpscl8VZqY*hieNiqOX(*(O}C8oihN(z2Szop)Xn^(z|mS;in8C_^{$2Cf; z8SP$ZJAoV+UZud5ZSV8Q+KbO(88q0?9zT2n)b3Ox?F_8`LP1fc^+G~^tpV@%2x|&U z7cBpMG=Ks;0}W{Bsd&-#)gp+Al35n$H)hk?SV1tR;DQBl@cP%S?y5hH)Kp1m+=b^W zV@Ps%?LGoQGQcGksdudbcsZNx9Fw5`wKF?C+nMsht@=RoRp4AJN4GMzw|D+HM|Z1> z&us5X(ftB*`XYFxz{>N6yToI4+smEN-I2F!2aQxiFQ<&P^hbyM;0Hf=UvT)izVqt? z0QerST}T%Z`{MCQ|BFBOPs*ns{e+6g8s}qoW60yB?S8uw$yCXNfl8$MvOgg}S*JWy z=PtP3A+7b@irB;!S0##1CPHPhy|>?9{HrFnT8mZ_LT@+iyY@~+(nM(|&F>8}4xUE3 zvOV`>*&b%#A&^@>Zp~cFF85PnE_ffwUh1u`{p}H>U`_j@!`rPQavX<*XFEU&zjljE0NT#rJQp>}1r4@x74RWL*#ykRJ(Dom zPh0Z7PIj$4=4AOf6<%);*L(jQ>_j`sy2u+SChnw=mTU3bN-K($?SS1u9nmXzK}HmL zPIl4I;{6JL=fi-xe1`|j3<1@zw#Rb(E!U*b`B#0QFndhK3`<}G~0}7NXfQzjZ z>WnNT=Bj)mkdf)=l{L-w*R>6FuEw6#s`g*C!K~L|{pqc(Tf5oDKY8re{IIpX!q{qY z)9BpoZ>_;16=XzuN33#~NO3))752i;rS?nz^gnxv`hRP&`*D4z*9QRbnJ-n9AB%{6 zF~<-&DzzO`mM$TKh@}Q`V^7=Bx1k8Xe@Km(M`4Eb1$wBjzJGHI?)N!>3N9l_`b|qa zGoJoxw}LqzeRYrV-8-Mn9k)m(;Keg_$jGH-)``6O!H>x9Xd^SmgBVfLh{lPR{?9T0 zCd5!jg#5Xd*iaE2GR7gFK7A4qj|h)|f9DA;GaaPnKKlR~{8`KR0-8dc**G)>&&Q`D zbJUmVu&R#Xg5@ga3047zx*_EXVIKaAZj$ayyiIck6%pwpV*v}@$8pGUJk$f-Io#qV z^v)o`)E-QU`-=M>dp_NYLBkb7hg&k7&$CSE#q(*8N0|2yFi+|M>0_K#aIGbJXryB-!C{0e1A+>wmJ=N(l?McIh&m_me82Y83BbB`*)~@ z7V`?cHtKc*3}fs|AddY3Cc0S>ZEgWv*sa8`RDQW^^!K*o;gU3f82(C+*RTDo=vw@^$2sqls1(zkbC0FP z%_m_Df%Y<$Ym{2)-~8WNF6?`Zl!7fPZ6JXpSoXWc)#L@ejxoB-msAQ=MsG&b6xrXgp zV3ix|f+^u;e*(P16Uu)j_cY6;nP!=RN6`xzgy^b!8yn-sTw`Cpw*4e%!zF08-jDO; zy7H}Qjk249)ri#JSa^xu+ilT(R<3--+2NO_R|9_H8#Z3l*E$5qJqQ=e_1DP0E}-YHiRfIj{F& z{&mAl?d=mfq;5v&l|g{*zXb=D*oxSKg{?#De`ivf24aba9jF!ORG2ry zd`_`x$?Go>IYam#52TK>bUTE`qi)DFS$5Y>6M7yugmpCP%Ii$+>o;C?X45kDc9!dh6N z1w0+c;Xuh*;Kw{;e1>GS|J<7t-vRgBsz8eY4qyIdD}qp}WEwz<&j7a%%!>xtflUe~ z;W&=4xG*^mO-;=TOBI;$K6>h?Ilu1)S+Xg0}u|)wgyP zKdrrcZ9GgQwEA3BOLbKe&kMleI@XHkYqnAF-sBRywO6-S$>UeusWanx0A7KHvM?Y4 zXi)TU2wFKd+rVi(w`;vcL$kd#q(A`WB=*33sH)KIwk5m7Y$gz)#*y~nZlz>iA8HA! zSRY1>fFhza&gD#CJgRA)uBCyG^T{>Y$R1Kb0o6+QRihw!bdDb)>BfKwPW`U+dNc7K zGcFdO=N1{?F8%TSn<-O?mb&Fxokcm1K@ZqqAgu^6Mv$y%?gz%miVrL6CJL&ab1a@X zD#oqVT!W7Ig_f-oYYR`j(5R)41)fXuTTXqDB;bs{;Xd@=Bev<0C|0BA(@=59zZ!P?s3 znaO^AVKB=Efz$|sYwCu}R| z`cx<6-*oBr)?V_aNB@Swu=bhzX5`E4=cjJ+&0N{W>L$eH^B?MlZbLsi@Sq#x44Sks3!~7kLh^n+K0Lichkfcgx z<+dU~pNh?85$7On7N5^E>#U%ACG5Gy0OhJjp4#B1OPgZp%ikjPhmZXEJS&oZRAA)F zVPA=PCdRGJ-T&v9cRm25(6FI_S13}t2P+hJbMY=t=LV&g%rvGp~sJ8h;gAtw< z76Ad8lo_!kh_NQzL3*$(QO~vjCUYFF(N|huFdZ~2a0kH#{kfIqyE00U2k?X( z)32J!A!F-TfuaseqVv$OdL7kTBjJAoXsphvY8RsqQ6mEepuGw3jYbXN7De)C(gHIn zSCD;eS%C~`6?73CcNUaesx{%($0={plC2d~dSdQ=xhvM|X|<@bjiixff`4bjxZa1p zHY@09bb*Ootkp7;kCy31KWNT3RG`x|s$Dn1e7{Con{BbH1?7R$P0{&539Rh@2L$(2j-`HIkuP7{FoXwF(U$Gdu36CUhYG% z>wcOqnD4DPW&^8TSD)9Hrfpd&2csfix~-cKa`x$Iq&k6lqu_Yeee%nNjAhA;#0Nyy zSM>mWnTm-BxQGi+&oJSJ2`XzEHO%-A9df9rWE<#V2yb71{k6Xz!tO$fx)7u*Lbc>O z=yOD6wlH<~$p1q%7(~@j34$@5b5YtoO>@mM<*$P%L5iritRZ|F5Z7R?rQXgkSqI|~ zLVj76K)9R{)n3vfCW9Y_W?w^FROY^tV>U(wpi&*_S|BQItSr(J!d>Gy9&QHT0^|kA zGb{L?zw4S32+0}jQ6dOr#46Cp4p8e5dtrG%v{9+AhDr+F$tbmb=)jALwSbhlm8z)t zQ*jmGHn1}Gfrzb{>m6wZ-2{H*I%0Et5s7tdGKR<)ha8B30mCVLPHnk{@2z<&)R!Mz z9FitPe=7>UG$y8L0*SWt{%ZES2o-?=l$V*yDj^XwvtwF zDKh1p)_Cuh3{6zsz8SYi@!B8UWuy%l0d1;rBBZfR)?S>pe@y@>3}6EwKtKC@qKY*0Pru!A|pE1)-cB>L7TD<)^|?QYohN38WgR} z6$Gt(3}B^%EVrl;fD87TpFbn}#CxP;f>jo=2C`b>MP3F0PTbL%a6_w|xN%?*pj9g5 zM3k!pYG96QC)c@`^Z!C{GoTWAE|d>{u2V*Bc>M{vX|Byp0gD%FB0E`Q%GeOR65z6N z!Bj5nV_AVH)(8uz%?;!x6<{{8&iEY>Y;YpzJx!6)kmTFRe_XlF<`SYs<2#4TtDyiq z9{8jn#FL5GZJpIPA~G1$zEE%5il**!zY85jbD5XNoxHnLANF&<_($X)zP-J!{Nwsw zt`7jn0Q<@eS2o$8iS@w%;iR+PAxm4`R9nBIqAgRgaeyU6(|$#Q-#a8{Bp(VE^>4%NfdRTb?0aP_o?#0Ni zC65TIXD0GEJk_1S_I#+F=k(U8+B}z~_CB=KGq|_&L`psgkN(->GkpDRN%K)h;J7(` zrHwIct);4>%ftf>ipg9{MMDD*Kn7zNJOH}~;5UzHu(Hi^Yo3pQdQw3OSEc(y#Cr(7 z&=9dOW?>01hMU08HKVOz7Qbq)-9^INf@koXYvw)s!-z_tAzx&LA;K9Cd^wD$XNjey zNq8psT8hAYLf{gDD9#KtD1u}GmPyLpwIpK_Bsi#9E&vmeibHWcjteO9cpQ;pFxr^> zWH9nWM8|M;*g+^sMu^PnA*@2cVisjb@ET1-eM-+`XIp!uv{CUwW-Va)b3^yQ$Pr=4 zbu^ecZSNL<@{e*=JD3Y*tA-66$SNSjHPQ4%?RR%;Ag;~6K3_w(0}KSX<*WDaza9a- z{nX$H>{q?MU*xo(v=BD?-K~eT|0G9HX(@ig31@N2n-$=el5-tWCPH*?63FFd=N6x* zHDI;0&+1k~=`dzqp3^+`N+^z^T!kcr^RiZ8fP#j>0d5RP4x|I2OH3AMk0fcy)nu3` zxwgkpb`y^L6Y|+FBLN@aa|Ycj$+Kk78|8;oP}&q?_jxzs@rbQ*3niL1T)3Umd7h=-o`A? zldY58EPe+YiBj3N7pc5k-mb{`75^}|Sh(jcZzN>7^51cqvZ5v~hAFB$o>WSBa=f z5z%6Q$c#9j-HwC3(fli#7w6-R@xa!f-i|(+E(~18La5&fBfseI&2R4<8`ojm4`@r_ zpU-K_18~*t(tyK{e0j<`(!SyuHw7Ibgqo~*k|x-NLl>?s0=tP!Sz^=V7(5kuS79ac`{Paa^nZb{J#N1C}kfGI@bIB1thFEh5254(} zCIQv(v0oL0&P-Os1#rK2H}6LTe)MUr*}#;!raUmFY0_@#Dj-PP7vtn<8ojOVN+A{P zKR_e5tN?5{3N;MCAerxEcuQ+2Ze-#Nw}o$@xNvdkNHPSPgW!-Ds}U;b0W<$_Q&7YT zh2ZjdTTA4|S(WP0K?!817-3aG0(&k) z(oE-`YT^j1FQ3P7wFYum@kcr*(ZNFXyhZOd9c>5Y`aI5uk?sI0km zI&u|AV3mh;;Cr;(i_B;hBw1ZqANHD>RM0-#M;QL!ycQ%gfmlwWR_8DZCZg)m0RWb2 zj2kQxfFiZ{LDpBA-(h3G`NICYRUmsNDMQb{xFh(Rcx5SD-<)_A3ul60kPZXQG$;0nmMxj{UN zonqg>16kBgO|RT0^{CP3OMt)&9lImS`@CtdgjZKuRc;_a&ecZx`S>)Gug|A`x&Ds) z)KC7Fiu_DA>O=Vd{;v-UfX{pBJWu>((sn1$o_3mD8&G3%|IDQk@zMItTOVB^z=VhC z8|aPg57WeSB>38{Mz;6iu@XthaMKFKUh+Jjsrfe1N}(1)R+!(XRu>v`PXp%~pJS*8 z`6F`de&ZD%U_!I4BG2=b^Z88R#sxl)4FJ#phpD;Eqepgs1o*HbOR|}bG0e{IY8iz)ffsy9^ zLx-oF8bvfgCup1QoA*X{bJ^7!9c3!J`{Yyk1i6>=0P*_6*v_)wmg2?PCE4*H8zvuzzz;+RD9{+II1FlvWxR1B5HLDEj+(Rwn%- znsT>FMd4Qs7Xkxby-K5QB&(}5S_O@+n|YJLys^v;fe-0Ysog0g1ue6Hu#|%uURPz#R}d`???#K45|O5oA}AYtxUbFVM~02gXv=AI(mG@&qq7uU{HWe;=h7_ILBMQ$|y zxlS0gY5b(srsE!~)*9h4+{ct)3d=$1KnK^wCh^%eyOv)-sNPQG-ln@Vs`0F58xDEb zo2$XTZDW^Z+2t-8PGekUvLs+u*|?7}_wAJMF%N z?C2q%5qTKZ54GtVoS5}0)s>7LGhx(*oiWK;_H*i{3siG^=#|jleW^rv@Gs?3{Ux+- zZMJ&QTD^h!ga%mSWuQEBiOe;#0$V2IlWP?LPU{#VR)PK|gk6&egf~)SaE#%A0n8L7 z1Xi8N2+SE8hv?6mD+1GT5W(g7>&3OiIzl)V*T_B|nu4^3=6MV)mxE{s)=edAbrD;CiJC5P0`vR@E<&5chp1!8T#sOkFnC_0&Jx_w8%5&S6|wZ>49^E^E*qDn^iN6^G0tPC7*qP;>H1T5C+v@-oyK^%uA z0_zBqG-J`}0sqF(wts9<(8pN<{vhTa*#-ax__{PXK8?`Q_;@07#UKH=%<3h_D+TA& zUYL-0Ah7u^1{r+`VNA%!SrE;d%fyI~4HVM@)lK;ALO#sLnMyRPsf}T6zOAg`2i;PN z{GYGBlJ2V!O6|KsBGs=Z8ErH?pq<_~vF*Ik7Vry`WdV+29*Kp|?^nwMYA>(N)@qbu zozdoAV2q-F9pVUP+SqcfGPAHS5A8S$6x9d-o}fj%4lz-d?`C!gl$XF;WC%(DGWH<} z99|b_Zdt(9_j(TRXGXbj_sQ`wEC2u2XD(6G;*WvKMQ0>HgcO2e(pDHo8XcB!*TTzY zkv$;#fowD?Dy#7yL(D0+KY$2Ai2ojJZd+!`5E5mHRT53i0Xy7(2gCe6jb$g4Y_; zOn>M1{uNXGN=5#>iTqpgaebca0|5BEm(4l#H-7!s;it#yQ|P&#cc1O-X!GjVZ-(I9U$O;N9#ea zm!Eu5K8@uL8i>{ST(5puGYD||MmE?r91H+Ctqg%zDu=uXqRFoW4#sk(2NnRw7&7OX zGl@v*W+IOe@WeuRT<%l;I5f53BYeom8gf3L5eJ~od1%8$r1{IwI8=Z}gQpY8}Iq7gs|{0#*5G3tG2)14O%pI0!<*`6}{ zOyBWjMlAu289wn4PQ@xd0S-xKs1T0;X$=q6WCm5#Qzl9YO>2d7Z8Byhs2WQo5~lZ7 ze(?^rn4D{hozgUljw6_?b9w}S02L936aWxM$A<=Rs5dFK=e4*)01p=qMSDdKg&FRW zu{>)5B8RI4_y8a*tW;8OU?!p?V?rRvBHS>r4kGYmGAK1ZEQ}Tqpvrs@3C!ERWD%Qx zV$W_3K-I^1@awmm{i@9;rq*7(+@)+$+2Sx#AM^1Sq$ZYQ10*`AMKK6e@B58+6?cQXj(C$qOMJ}i!7JX`$a_5(t6CVRcY%WGzb8NqR-IoyDOA*aG=#L zNk1f%%3yhlGr0tNq`xz%bb(2ZtmY*_q&6!kw9p+}wWnZVpeil? z%08#eO<3k>qRcV$@Sw@8;<}{O^!TgVy>7sWT6dlIYWt0abYc&?0Bwc28TAYIQ__tw z*F|n3RT6WV+*DIIxc0V}8x-?tds%n%qPRR~i(_3-&Q)Vz?jo|r8A@~9=26WvMeOZu zph6dtfe&0#1a52Qj+%Cy*a+`GiYLpX;uuE?=r|N?hWY(6c|Mn@nvCNJD*+4BZOu7> zsk({CoR+4agSGEgt^^S19%1f(B7GbQjVs*=!i+Tzj5%lej`L|`(^KnVZ`ip4Xt1`P zAsEi*6lh#$_~|?6prWSPrqtjY0s+oo0GCYF=wrD;AYh@0YoqlLkr~X#a6wvQy)UVL zJwn@^^I#%2D@wi#;03OX;q1uFAc9NQNm}z*TPvbRFO#%xNJiUC^a$$^6H&NwEKfTq zLK?v(sY!RiyUd5Da*9UD7y-}_j$DtDYchSv#d2Im1b+zJAvtc3=@1zJB1V8{SWFBJ z@Wjh@BaIUNMGy$>2rvyB)CZZf8GxK}77GACuYBV@K8tm<6x!q5f+f7P3>?XA5hOS1e zF;~jel4b1p!Dgb`;@aHI@k?v4R`+$>PU)MzyC2De!h|hc3I72nmA`fzdF~gBIamq{Zu9v+O^c^0E7roYv^Yv(E5?dX_q4Lq;)`-mV9Tp_L|5o?c6!P z+Sd%0RbcHl-!u>XoTx>hGRoC=LwYn`v~2y&rI#Z8GtRL)nvKrpIgh>Dy9qjzoB)~N zsZRav-~5k7e)f-u$WO_~_4%%k7yzI7`oRwl`4hkV)AkR4Ji~cJmG@g>a+$roQlOMU4*UUz+d9bgz73uD#3tBl> z6-2NYkkt=tL!^7Fg9`_Hn8eT1frRrsk|k=7D)m&qx5Pff@el zf{3z?o@7A2P&YuBTj|>#*$l==jm}yt0>x?eSHuO(H056Y7lJ7t^rcph`H)EO2f!qR z-Se!#f4O}%{CFi)XgEZ{6j|*vqhN~?HUlN}GNa&N3tRy*=wW1bV0QyE5j`Th_5}Du z^;oTL7wk*>p5TH=1)7X<-6{j~I0Beijj;jKCCh;b1Th?Ac-)Ee>?ZIr2W%mOpo|jh zA4QM!?bi$r=yNe>=a&^gGNZIF+Z^+@EtN+J4woOVLa>RXxpeKFjm|MmA#MNOjZS;O<@&`4u-L}1XTz;07QKE27VfX4is@!S zw2#{caCLh-{hJy3&|do8n>-^uyp5}X{~ciA z?Zz<@V2SjSx6q9qSb;Bo)(#OX#egZ?mf~5m>cG4fttqC7EzNj**yle7DM0eo?pyV( zsLpG~aO-fc*BfS~`;l+_WG*j1d)cb0!CXpI$_fX_+Bf$Bq4mf2jS4WSGo^zD-5!YP z_HH4>VA;L^8r4(X_}=#~yD9MQ-sRHT7gw^r&jW*Dn|p!YZkBIsz21Djf9D2}q4A03 zz|??XTg;LFGUw7?`!y4hKW!ra<~OnUAJ=zyeOLhefm};|pofeIbg3jr)c@nMU|03> zOCt3fUEp7f6q0Remt%Ll^D4r(ZKp6?L`M= zH^sVVfU01v(`oCT7i;an%F!i0+NhT(smNU)5WCdhAq&nSFJ^MiGXkKE{9Ocrg*<># znk&*!EY`M$kh~lf5M&21XsVI^ZaPyp=aR=Kk7zFjQ+g|RfKd_7=Zv6t;%5JItz?{R zdV0Zej0z&SB5MBO0_vPgG>UW0dHIoz<8X~|3YN}z%@dmaST{)7WbOlxpj2AXsFhOb z8LyRMD*UsjL6mfb5J8H)0ug)ype2(E9%z>S?_(gi;w&v;SSE;ykD1n5hpD}mBVxo# zkR~W+8*RyWxMjkcCgTu3b<2!b!-Dw6Z-ahuHDXj77;4Q8gT?t; zKT$dXRuF(2C1XJftz~T%1RPajumM=cJ1x4@bzaz&T1^d1a*7=@E2$JLfc_tZ#*sm z02>FB^Q))HvlJ)*ASemN>0`eESVmSM?9N~@;SL^B9n)y6J^dgUnAFybqyWz`1XA;U zH>I-D1pv2ugpShO!<3#NHs32azfsSfc_MnUWm@QMi@Drl4e)q7FhE_12=%sB83hR1 z>0<=iDLMB>?21u43R*%l2L#-kwJkJnrQxpyhwb5+2)faEt!F9gDbYAHs!d>TY1ua_ z%=A*sk`==l2&<5#Ms`0KiyM=Bt2(PcewLtX=`E1JM7E&Hs!7N>(wHyiO)ns=C{4>o z3-WBK^_v=gX|WC9z|>gtD@o%wYW0iBjr&%gU#$g{?Z)pIdw!oWZxf+$2Yu`=OKLTS zzP-%1G{(-%%$BK|{ru1WQnllyiNeSAU0)v-0N?Ya+b;X+t0{l&|NXoANB@aWYRd*~ zhmQB{n?1?;%Zj+ZhPLwii5R@lFuXE4}68t&(Y( z*0M}H2gV??9hhX4e-ZyYXXU!qQK&jH0*r{p|9qYvP)^+KV9u!$Lb?a)TWV&`vzV!d zpye43%B*}Xw~%;#{S;U?cm@FnFf3v;%+#EFTWs4hDj}q=C89M)D?o!}>a^B~+44g) z1b)xoUG5f$iEut=rUZOGPdT4wx13pUQSd;2X69!9)6%75O$Ro{aBZRs=le;l>Ds|& zr4W$KEP$U1$Y{Cl2n-8ze%>)@br6xh*9zAI?7d)u;f~_;QSrOU=}N2XzPu)+Qjwc2!mD0Mu8uqm`lu+)^WLX?I_FRd z8Tr;MZ=E_Tqn=0U1Xkl(F!3j$7s4J=`(e#;43?P)189+mtW~aI_>Qv)Ch2BL16KmF zE9=OtA(4WB!Hfc63v{v@K42wa7vLzZwR*qC2_Rn+ato=8HZXrA*VVu@Aj>lx#7T2( zrzZ||T;h#n%VKcL?4R7@tV(Hv6XSD1nTWPA*~t`$cMENI53qh;xBkFSL2a*`x~D8G zmJ*|n%?&?GjZQ)P!oT)dYAV>2Vy!8#(S*~^Ud#C_n8~JUeWK>zRE^_ zwN`=V?cIvE+SLxD*3WIS{gBYd9%0s`fKl2O! zo0#lBr)Jyo`nbNg>jMDzo-f<%hSr+$+rRx=vgFG=EQ_=?_9){}N64N=9vxgOt%1L^ zF^9=@mq5H9Z!E~}h@j9L7fQ{vBr%1}>|U?0b6~N0j$z_9T=_W(vsxE!=pbhay#NqP z0OE$6(HOK7m!4o`pC)H3sihQ?GKK4W>kUl1T`CRs?iAG?__Q1~`~7(tqBcaV_}~fd>H)q**I)s3D=bXN^1u8azV)b%Rx;nzLM( z*iSyeV`yok#f4Ul?-GE4#yE`#tO#v2fD(+;7~YpppB{2NTupzT?w&Bi6#)YFS}V&+ z23;(J4L+aG%z_|8^-q$2cPW zUjR633R1M8Jp5%BC5{KsH+Rt~)-2Tq?sV@eN_K{kU3XMQ?(-TOq8kJ_W^9-cHd@e7 zl=~iLx}~YRi+;bqWcggRo5N3C81hyG!C!jwNCS$mZMg%ZU)XS!%Z>`9s;w8Og=%Vb z?`^1mm&`ZDby7$8tfhaKxDrP23;`Src3^()FpqYUZ`K?=P?yzN#dr~pZ*UbP{lI6t zG~ga@Wv)ff@>mp52rw7I*P=6ufwlC(*S${?8-M^DSV?Tc*)C#X9(V;Yu)0w8<_cp7 z_^Q?J8b_rSBPn3JA#(`)mvv=6CP~AvCTj56z2<&$F)=Fw=F+S;-lT(e5O$X=*##(^ z?=4q*f+D;Jq;G3YezWUjG*_5#Zt7fTjnGPhLaBWwev@Mp6fLCn&HFS(;j(_(cY8lG zYWPzy*J^{$Kd;BDSUqU$!OrKrZ|8wY?VH9V)B5b7#-#`mQpLoETkrw-r@G#dpdM5N6Gx-T&Mij{C|kZZ_3Mk@o{}G*9QRbnJ-lp zTZjDS?|h-oG^a-G3brN!n48B`nLGzkOfx8EW|!>9XM-Ykv!xqOcr|fs@W#R`8hkEl(ecb`=G^$Gmg-Cg(O>?GX zS!lw^-x>!*L!S*1=l%0MPZ=Zp)b)^Ygb)@!-E+6Y&AkG+8Bs zTZMLC&6Z5p{+o#CT1(+Jux0@$=bYJgfC%%P*~hh}o7Jur0{qMv0v6!G`J5@Vonawh z%fx2nxIYR=fSUlATmwKL9{kUi3x3dA7n2T6cKF|i%R;6UTqg4j=03IeNRwbP9-3(j zXIPVnNCu@Gs`7Yvnf_eNvy@g1?M0Bi4BSmt0oZn6n7aMH0yx2)qWT~^??yDJgyLO*z57&sw5`A{q%`gKwdNOJUd>32$GyhLw$UWo z%M2@@Uzf@f-OhS!o0t7`yWk+(+~MgB?#Q{UubY?B?S=8y6rPHPmL72}lW8WUxkkY_ zznOQ4b0M_&B)ir-2H2{k>4s{#;3Ey%k|w*^!8 zyPZso^@S+XX{C|?U(NY08T7@#781rwTXK8iagW$^E_?g!-xHC)qU@aXaee>S2LSMy zFF#PfmY?~fYU-KlfS)3SifO%dDSz4;x)UAJ6gI@ywi0eCW^=r*6Jj*9zBdgRp}1^9T+GFF z9+1|kkk>j06P-4U(Z*cjo`wndGHF_#Rf`3ra~wwq)-|>;19j#sG|bRv92&vn5RudOifIi}(k@(S#qTxj;D@hX+8#T@I!LEityw zx2K%vDJJDkFi(%qkm-&hXdevuup$5w%!SOK(0WVoRhIS7IYWy*5+qPnk>OSlx%Sn~ z{-5WW0=J1}T!nd_0^t_!A1WFGa)jg=8rB&AC|#I8@XiD%P=bL)#sTmdBZN!U@&yH$ zfs6to6|fIXGz*3+EIVAgKWe`v!-IY3?aoXSI=YY=^sx3y2NsaFEYnR|PJZn@lB=2q zFNJB$2fHlwtFYG4_&3sLwL_?lO3!B@T6BqNNLfR5+Fg0It4+hBCL|py`q!$dc4t4i z&5~_SfjNg}XC6y1ZdUx+zAFg8XHgfh%hDt7Ghfaiz=ix$W(Ss-+Q%-!=cAh>&x*<> zl~}b{TRA(UOl{`#i#2k26k6*W|Cn;!k~GOp#X00N=Db1I#L6XrW|IBqYakV3V4g2* zb>?kYjb`;e0$h`Ru3N79zOG;o+&Wk&fNkfxiI%lOYj-@6tV*>b?^9}3lhQ=&j7GaIVws&BuVBo)q2g_kV@LgJ$sXVx47GKC#U0^R4jsjjZ zI$ls=%wDOd^Y`@d^t&06L1zzs;!QBYbk+GsJqof3Ro z^H@X4J8O>J{fJC|4rPz;t@rU9;0Ef!Q_1Ufu0O0do$s7FM@BKKiY+a?DLG%{e z7@4bR&&*n)oE;vPo&4GDcHDg%HztQa+-^$~yCFNRhV6%fG`IVk3P){w=T$R^$#?9({EONIN&AG-nWB;gVjadLBgCsW={BzgaB0p#1rFYO%il1fS^ZHN;+ieS{ z>~crMwm?((P9WpWD>O#?yW03Uy(wqEx`}76py-*sdRqjd&BjQ=?Xy39-;Q4Y$ngg0 zzMgpu8q|ZA`}^44^d0Y$NJ4#}?%P#j>Zis%;M`x%9S5d}fP?4hT>y66BCR zJ~`g~`_g9#AUH0UpdvEuFWlN3itqow>^ICk1RKe$()X_+gduG z(?rCM!_&Ei*|w%RzDNej8}KndmmCr49p=ty9v=XJAqZZY`6C6Lq?R0q(lQA@@gsxg zRWy7_^L2NR@R!h5?|Vd^7q#W)|IFaIJAj(Zh*FK%0HTr>5lcK$^>GYI>+x;QR#*y* zh&H^UUZZxPkI?c1Tv{ohVh(2KMhr)efMOw=6 zJ*7+o$IuiSQpnF>kRnG|2XItsiMdcktc%XJHWn$hpV#Z6LEkaDoub`HrP@kjvQvby zL$cB$>_fv_6*DLz1p*M*gtlSqsUer%nj5tDcD;1u68RfMiUaXDJzK^h@y{gy_e-?*j zXSLCxm$RC4+Q9Hvq=dk|>r`(%u@)cf?HR4y!o=?c2Wk6IL}ffanaKapME<;r{8lOuPSncn}ZIFl?bP4)S)sSLyqGikK-Z7 zapahbK#)J&Kpb_UywGTr!0u}uCjBPM<1b(2BAogXf%y|+JN*LQX>dAm%$`C_dsdHC@F+!sXa=L(khbFT@233P|BPj4Ec{@(}> zpqH*3)vQ+pZaY|iwKt6e)TN*I%6f`+b^*OIoe-GL#fylhaW~q&38F20yQEJ4de+ip z_2V`7ZkrJZ`dg~MR2W>qAqLuR)(WqoIN}qu5-@yV0&avfvaW*gmsX#sJNs&lrg0J4 zgi*N&`B_y~4-1gH4t2022R7WlBqPO>RRRDPBhl}UG276?-M{>ffAee+7}au#f$_l= z=8HbiJGt6b{r-jJE^9MhQqtOY3dP|Fip(Y}3f-3(`ti0b6k>8V#>+@V60KjLyzWnr% zKlO+InEdVk=r>G;zKQA--&)oV1LhY}%M#FIlR-OUn323U!TDW`0QWcMwfnTw>5zZw zccXuuly!)K&&h!vkXD5~<8FZose*Rim(lumu(kFV^V8T8>S`XTbm;azXwA_+hQy>^ zu!1b4ar#|g4M7XS9_C;o1sn;lC~M9cDd{Brx;;=|nH)mfJH~MByt)AHf$v;$n&(v2 zp(diww=;z`XOhCq-2_6nLNHrlJGvr8p9^Cy?8E(FgpfC918ZLvo%1vt(ip2pWYdJT&<`i3j*DrE|SWCunq);sq>GH6= zRKgUt{Aja2fgit?z^x)JXjb;cEg~$_9r`#fTXIg`#jJdm@kW%1tGLA zXa3goF`wrtnLaQw54eSf_hH6gXv@u307Z=2OIVGpv_4>_UPrZyP2t!`a5t28aWjQt?G5&nDIoG9)y^%K3-;H~?@WvcuwgLvPEb$w(fCQOvt|`>o%s75 zH`o$Ql!&CT~2x2TaFmfVzUaq%_TA$euf=OL&nV-Jx0W|pR zu1b$MNYEA)u`siT`6mwGF`S7$Nc%JA3fB!5`ez02i+*OHz6DO-V$5d)?~o?Q0Nhx( zWt_8^&G=uYbc{|TTf;Gq()K4~Qsd9o54=y)r`T#AW!`Umhw!+HaaA2*HBqr0#u!~7 z1ieTL1P1d=WCiiG0$fZ$Raxl6{!GGfy_9HJ3KWvUBvB6$8lN1GtaG&6{9e~xX)C*l zlmHFbZnj>@a$Zw!l-=o5QtQh>Ql*xuI@gyILRusA?56I^rwfZNviBef_j?zYcsGgf zd)BtVbouvIgoxTYE8knZ%`-9NX|aXpQ{#*U(buJ@WT4b07}D9h;#>gDMUXXz1JGKs zFvwSZO`51I{9A~rBIzQWu>=B~ZUc3rVRU(#tgrIW1-M)9Ew~eq8$-2NlANrRe9Q0M z@EL%C%1!%fE*a<{ZwLn=L9;9$Pk|d?MO!Znl5Ikhw03BL7PL+fV}_?3yQgT8<-TkJ z*b8B-{%Qgc*)TUjt9nR7pGijBkZtR53}YBWW2|(5Nb~K9O^YE|E{-gyp}7Li8gB!1sTB007_P^>P6}r~KM~ z{BO&Y^)x>{w{ugIabgCRc=;H=R*E7Vzlj&_Y?&o55GMXTJJ zu)-*Pi&#zq4&FVE!?o>Jri%!W24T%qQae7E3%AQer{F+-a@qKh(M2CwPnaU6$?$Ke(tkzK&u2b_T)4ur%w`TF&M zk{)={tr{fD^x%FY*TS-umIIt_PARydG6Sr%S4M?68WC3jYz*QlREj_iAOJB4Xz!%| zYJh6Odz=V`GW}w~4WE!zfcLAmKw_5|C%Ko2X5>EK%>Hu#NE^OUhlcVdh}ZX)Mt#L| z@3bYxB!JCk8L?f>%$rz9tZx=Vb~9zRGPuxfx{Ahb8gzR8&dk5qS}AL>y%QVRZ;byo z$Ndq_rC~|0DQ*Sc?x@Q>_Hfgs-uLc`at}S+$Din8RY5OpT%}}#(n{9hsC16zbGg9A zr7Q@zYAP1m>Wpkp<5iko|D0K0Xf8PrVY;4~P_KNgIxVTTC!LG&nhT&fKw%5IB5l4v ztMZeMzPc4-R7}pg47~Rf78KxXY7C6Vc>^ESw*_D3lW!^Bf|46M}c;u zqD}pC11)cOAWYkiNv6`aS#4>1sO=xy))F&Pi4|ac=v>HBOJcm+Jtlh_J!{JakEG{s z_YV!GHotxTh!sjSf7|3Kyyjd8BL;HM#TInZ07B&vU@!vH$3i(C!d|-=12gu*1M)e;D0Rp4qVn8DaHv}L5 z2zUs06xyRH6K||^v1y%QqYcU%IOxVJf`NU-Z(V6s(T|PBr=U3kBPjFCTe74byg(hh zW|GNZFQqePDO6f7zrh0u)a^nrm8Wt&D;j8^H6hm*>4+pGh4#KwSQRG9#e(9|CI%6WCs9nU1#U2f}j)*uo9I* z!h}#@B_LYSFZ3g`3IO0uK%2zAO~7B&+e|07bA63315&o6O0n8fKXg9w%d%a* zMD9bQeeL1@d~3-mB7a4_cmJ-4{EK#J$j9})T#rBK2K@igRn+Y#ME>-@_OFPYHrJdd z%lm4% zJoP6-v%NOykc+mNxT*LwXv2pygR38iuDx}VJH{~hv>hX4C}`slkv-Zmh6lQPdwZt2 z`WQpb(*y7gXzGU-bUfUL{ya~4zCFEvhbFi%#)xS6<I4f&xCD#Uui;eQ+> z*P~5I3kP>Gv8Me`rb8s=PIG)7pB`?_F=71xrnIzFW-0;w9olzqQg9);N|ACR@BB4W|SAnx%+Mc5tUc7D(|9C38zu?jkX`O74s@vNw6ll9t)MVmry05$q$$cgYZn?19%3dY3MScW!u<4T%)cgRLVANso{+^Z+nb_ zYdz=#^E8P~h|v0r^_T@zrk-ul%I*rqcWc0{og>=HP+_fmUzK@MNppV^6R`k3W6gl1 zRAvNrA&K`J&Fz14NQ5jKU|FjuxWCjDRUK+z>i`qaT7OZX5t#`ki}aC*l|ko6pVpd_ zi`Cd&Rj-76qWKM!%CTmiK<*KtRvX&4#|og<6ax}>dvu+C45^3>vvt`2b^fmWH|3Ns zkAGD}{s$uR!*^TraeX(}hXug*d+j?!MBL2x|M~S_6j3`=^uQ@-mnY@IbY2)TI&Aza z2uIupS|1N7J=f7ill%R~NTq+j__AJlyuWk%*lO_He^Y5z!cSd~LPO)Tjj%7eBVb8g~50R)J(Rs^bZL4Tez zn&6~hp361FL+Y@M2=lHrcJ}Mj1f*VR`*auSSEpmyVCFkQ6{WL>LLlKF9P6|X^9hiu*g$8H@!0~t-p~1Hju)%AG zbpgN?9Ps{(2mX6LPX{Ih@f9fO+?;N{YD-j&EI7QKeW28r1a6xeQf&^?r zXii4AhKLE9tZ+fd+sCi_K(- zsR>5c86f{W)WHUnne&>wJ@8R?=ULq@I>20ECY*!E73+I-uB7z#dQHft@Zac(B;hWBU; zcg2mtZQzbVqmGp#0rQ4ecDMf4bN4JV;0x}hMqQlenqMGNys`PaXn*IjQSG7=SIPyJ z7~rj6Y!L8mI9}VoSecjmI;ZnND&B?#*yq0OMYA&6L~OS!7bV+i1CY_QlOkRf9Y;2o=RWix$P2#CSz0rCgO9%1kxARJn*7A`y8#JL1?rC2-! zr+9W00xSff5~C2O`P*b!t#z!04AP6f^RisU&lL{F{9ZJG&B1YN*WB*gGNtMQ9IpEf z^VRS*;hgkJ#ng-vFga>nH9U~NUN&A?mvP&1CSPFA4fwjm(};p~ziA?H*=jB+H~d=z zokDQdHqNr~(|vu{+pF!bO#Qe`P&n|_S$Uyo1*YAfHAR28=wcOLaOc}T=oV|$D;n<0 za~ux{h31OUS_&p6OT~Q`e;a-IfrRk?aeapC!vf&*T)Ai7-rnR_fAv@OkNorxRP`}D zcUNpH^MlU@$hxT@8s-(G?IpL3TSl(Lowow&NS1c+Gahg?Wn~7iQ;Gx_f=SGDFB@TR zO>e*k5X7t2Y1e?yWw(r| zy4_z9>D_R9Ann;Qu@Qowfbp@!I?P1QbEcbstyMvgEKISd$vl@L9Q51UDUZkDIp1dp z^V8GXA$mN(0~a8V62L zd!8$Rg5@BEik#1<9LFKY7;-)%4WBxrH-^d?ZVa|u=wD_sR5AdctywwmhlVRb_|Ti1 z%|l2=svxB819*{I+hceJhDGZ>Bk33T0F!xW(?z^aj34%zMJxeieK3)hl2AnC`8?BI z!uQ+0fKG{V=nGsul7cD}NMPa+uz^GcWtvoUAn$BKHK;PQzq|EP#eyihnl3@a zcwJ%?v~kpGCJ|D&MP(0@S!3ydM7`#cwWB6CF%K@^O1K$%_dB7u@KOXlUG&Zw_-bnqA&kx}RgVx;KgYVG%p7^{Z&JiES43Ff1sc`}7K^})fEE^fakinjwBFKw_Tk8; zHkCr8^3tdM0)%y%{A@M@>-5KjgtlZIst@_N#dyWM8^uLXo;tRsFp&yBqXDv9&C7Qn zQa9yDTYs$RF&5&`Stnw!5G-J|mM0cPz7sUP_9X!9zSj^Z1_(f_VZVS7UJ+PxT%i{(rL2k2mZf%{ zGQK)QfAa1y`MAE%>%#)zGhZHrZOT9Q3x7(?bfihD38l6rm2OGwa%HmW!!-cYUUMOt z`(<(^2(LVcbo+ZL%kIA4m-gBpli%+Q=Qq8<=z6xa5vy-z=WEq@YvO)Zba*`#grq@fc~gSpJtKT1FC&gD?dAEbX01RLFe1;keKz3C?y z@tuz9g}m)ojZJ6F?wGs!YI6NGLlVBYY9I870dgs;g3*C~jl1GXu<}`iEW)Ddt-yX) z=k;#Q&Hhe})c!jQ@b+#Em$!M5S_y4gBCMG%=eUWrm2-3YbeTB^3AtG~ST4>WFzsx1 zm|A$ECz56OH>5kILb3_|zP6ds*gK0|(40V(5DZQE26(_|t2{Drrs07AIZ%;A{o>em z1rwCEHXRtLwis#ZZ8H$5VIW`yqtdMJN)Svp0sNnU0mO!Z@Gmq!Nit#%GT>%gW}5w| zHGJjQcI%fS6udu+et>2p!-^L-?srHdee}ip_uA$RU~&sZ`ba@n&K5Si^@VWd(&&zs z_J3o&Xc#o-j%nv^X;GhP-OAl{r32k6pvD7mB5xlqrSk|IFWF1Wmv>5A;Li=BxV5;f z^4>WE)votqk6}Iaz6{xUY=%=3D~$tv;%w;0^*OH(0N^uUKm6ejTO7Tr2NUsMFZ6m~gdf{6=`&xbnGr!(vwZYdl)SXrq5r z>^@J;^1#Y~G~1sHhjSw{Qnf+w5rZ}x&qxZR$2h7kbsuT8;heJ{P>Cg2YTP0GzP&x= z8Eyw+B4ZqKuG59l;kgUfihyg=WIS9`{d~gweI}!Y=R}q~-=3*a_k$P#18180^T^NX zLZCn>6p`n7%JW3*fJ#NR&K17vVM@LvttoV%+EaCeF15>1YY&AY3UUTa0J|4>g0ZY=D)6=6c}0 zFvEfYX&qOj`8&co1cC>AiRY@Ih?pY})Kzp8jJ%Q-M@8fqkyrb|!|Wn16C-WB&J~X> zB;ejX$U%rMcas>|NccvC^83h_jcLlJ?PpM=2LsegT^DiJ>N&jPCfnQnOR4R6*&f&h zmZ%voZ0-SV{4CXi)&-poC6?~z+FW~ zhcM5da~!SCsx?A1tO5;V0~nTO04IH87_FaN zg~a4=d;xZosQ|Tgzck{#Y+lQnix1CHFK_uWcQ0y`I~iB3lUR>Pu9-c zL&WMvx3-fb`9c6!*lkwz(FdjXc&e|gF2jL6I{#fz**j{|%Do{rtl`i0Fe|N9f36!I zowWf{#IyUjsrgwL8Vxo1_x^p6x3^c6@8kNut`8T0KY;5vzK}0};zugB4rLk+R8rr6 zzghi>x|#CE9lY8~U>@%DGi6HJ&LFCo|F9E5wY@*Ax8LsS@20*xs>l!0se5bUdN*L( z;57zwU7y{l@daP!+6r<=u-p2BR~LrvQOY$WgUXBn4sL@mC25+j>Sp_MMxOU&@;smZ z+ZdwLng$m_v}@eGEpx6Eu-(m|`tFCt5577*%|FskA69=?5#$3sL+-oAc{j0h?hhY7L=EQ|K0#==MMI1U#)!^D2g zDdTataJ;H7;^ea2nL9vb=VMMPJa9v+%)POVgoZJL~u@QgmQ zKP=|%I3ft*$mok{@t_*+3=o=aWq`poR|dR-rU;fIh_VcGbsgc_A>j^jL_n~)GPohG zacHg$05M2qwUR!8k+cf5H&o{H6j7BCnIV|bVslv$Ei-=zOaT(WM_0t>AM=pu1i`?= zmBJi&LVKW@FmHI6v?B9)aWOrSRNcEr#OqnR^aqUMxR^#_44RgPtX~?+OB$V`2UiW? z#n@eOS8u2kQN zExw;xl^ZTQ&!Y<{w4vhF7Mz2cw7H+8N51^qbfr)&z8Z!RY^z{{J;T4DQs~)q1}3G`t#< zMcNkXWezDQTMBxER@BX9v65ivv9oGMlws4xHz5%MGF+-Io4Wc^?oitj$+DE(mtub4 z&qvlpyc@a&lqTQx%Et7@F_rGt8rB@p>-)C%(e7M~Xsy3!$#qU%(YSrp@u#9UolB2r zc6!Y0J>ogtw##XNE`Ggp|759TDr#bf{PHh9*81rgnZ|GsZ&+ZzSq;42R&qeJEzy}t|p*Ux$54l&4H)oo_h z>k+a9Afn$4pEd$|Y2Z#EJ6a-pP?Y{7kI8kcQnN@*Q}u<5!uYLLeQ$1I!8~kxv~k1( zVBb6)R;h?m!!klho9Rc6v~YM1Io-!l8G0B3)M+^2A_yN$@gN+o>E`w#p+OciH+38d zJiy2M2;Xp9E8PeZDhds`n{*#yVltly2&gj8GlTUZiaW-{4Cdt`V-X`3<2^$lEXq5djP)cF+wXJZMFb(LY>6^o!({-_y_a%MD|Arz@su0q$w~M zVuk;{w{5N^4=muJ;ZgwJb@N$=@cwxC5~uAyHbD#)8EcB@;o6ff9o7@8YYT>q2tw8{vv$28BPpurBXDhdihPz{awVx z5>Fr~$sXv`=ya3Y*{sD#2-UO?>#wmz!A5Gi9myWwJY)3t?lPiV-8QO;5Y-wCYoqN~ zb6F834P*5k8F<>6m!RGaK+qbo0m`s6Yp>KEO8NCg=h41h6VJu8Wp#&g#68%XILK*| zTH!dy7FDp!WLkj+EBygNgUW|DD0-TTyG6~UvoO=Wi3mV;*QDdVW_!?P$jcfiLb()Z z1HDUDE|dHwIE%SA@{)>w17T7sgdIr0qBfKuTy(PoIZ;u{O4}x5#J}Pi7h4Nl&fkb(}bkkv`jkQcPc`de5 z3URxrSjgxqlI|jKduhO0v?QGpIPhTuzT9ms4}034e#>%jbd0SNSYJab(acUCuh1iZ zOxnFYl`F21i>^ewP2DY3<_@BDceEQc#^koi1yXeI4SWFh(>)mCfD60axzJXBecq96=gn6xXnVaTB$KQhKl?3 zPf!G%YwB8F?c>v->L7%{r(VS5d_F}aG}UKCeP45?seD*+hIZcP{uw6t2>9k{{anNC z^^7QIWpFrX&|$DK!-7CWWQImxB>EPIk*#nEumWOSdtRsT3LkkxpsFo$Gknw$)qbtA zwlImZqpz|mmVt=#SYsd~0Y&CsOH@4G0!$|}1tQuHX82IVmrfT46^#rG&OjsL^gK^F z&okT2oR3*)ZPs~40Kz)_j}WpQM5zFjoCQHd(i|VD#E!$`Qmj=B3DN`txPlhm-v=;Z z$qZ>#Ce2MC>KN2cM3qi!m>4t9Y@zWE?WfFM5OK#9BA@1l+I^u}7%V~@dwwT(?DHJ7 ztoa2uW^c1QXhK_zGuAoXX1c^cVPnuX+M!#y`)`Fa?!s*xMXwG5z1)#~P)bDXOe7e3 zl}k~#WSsj-y}a+Jy?UE(Z4VWGcOjAS$jc$I1s6sx!hr z+MT@4%}PjeV&P^`=R|c>gwI|x5BA!jd!sh+ZOgIgbfLeR?3Y!&N{WC4*8|vqz@6_f zD`Z`W%DFYFOX!`QPcylW7th-}8dkgcw69FN|KakMB~zrNdtq>|8u_X}TtPF$->gn^ z)e=Cy6B$-=HB8o+W?41n(vT9?cYq?xy$ql{@pL-JWb5(L;L?3uMYs=b6-FHv3&U}zaqc+ zKl(S9*g8#hiH?1L7Do3C0{foZBz=!)!_w1@AkaA^MGPdFaA1V{SxekyZxR8@`%R8* zkY>wrPhvT(+7(eKw$<+1u!ANE5z}AY8DAbR{ zmJxPitW3ns(qZ}>8W{*QbFCDr6(R985bjt%<8h=$Kt0N!&vQh;H#2*DdW1Y?_IMmV z^~-e5Wos>&)8y^zr@^Nk?Gu#@R`>PWTbX%lnI^j|2J>y0n})Eg8Vn@T-o!C7M9$}w z=kpnwqYz|OGPvIvRs+6{BNbE#jLXbHjgT}gm=H1byiVP2u z*lhY#E24F1^wBpFA0Liw;9A+k&GBO=%sW@8~HK(xW9!U{lHD^ivE z$N(f8syXf=JxV;DSFq0*!kB16*dRbF z{l)2AXmmNxhZ>|iY@@$*fAup5FY*L6`kh0QmeEPO?Y@@HG=%$>OC+nRgR#pb{ruR! zbpWBpIQfD>>0WaTJ!K>;(abnKEDNd;EHDLdiep1qsM#j)Db6u;fdu2Av+1H0Eng(q zSpu4%^#JP%D8P`{KD0L0qDFhS4_xv21@Xn89Kr|_EVF9SN zI+KusF@5TDB6&=CT<^@$eN2&Vumgw~B@A3d-6uvScebh^3yP4t3a=q-uJ;7}&Ii$Q zXKPcQ91^~VQ2W{PD+VIZw8rbbYDeuA&JM5xlQo2N-)cQ?cv8*ZUOSKVU4x8%Q(E6y zI={d1fekQarv>!UItkmZ+`k(j^jeCz=?4!0_fCtSWtz}A=dBGy`)mA*onE%4*c1^} zd)3H~>wCLC0D#YZjUn>0KP&$;GaJX_DWY?KWEbu4E+(1&|4pFH4aYlAx3}cNUA1d7 zWxDD(_C&Xr0&d!SEv)%>wRREb7F4gaV|rh*?xbjchJCPO9{ZKr>68>*lMf**gGZW)&6RboSrOvrbpPsc3^||E z#yITpctkqDV0ldB@i=S@wPhx6Z%>(?_s#`CFm9n$*5N_)mh}L5X9TN*KR;pyoT<$> znW4cJGnuFR^Ci%=ryj$RbGLBck&!tUtlw9{^FrY5+*( zt{t(S;BU6oRH@RU#!!CqoI)3gsk5NEy2C7<8KV zeH#PxX`-pfOD2se$i;1-m4JIhb%>nTnya^7nUP-JfAzr5fR-%ZpWNhq?L?Kv@k#(2 zLz?ABhKOW!O_UOcbrD%gE+~(Q`|EOlV~3NBXGV4rVMhm(Z>N#c0L}V2T!H|Y`*O*; zoflfKmzM8A$5M_duoY_npju@BsC|`!vd|tdX*uXK?l7##sAqh}84fS2^GL+fN(i8B zxhGkb4dFNg@h#c2548Cwp?5{wO4ujlX+u#8#O2Q}UrWtjyojOVlmSd##+gz81X z4HQ7YKkX~YTHCk{nq~98Po9WO>35+MM-1-}-P^aXAs(-MTW^`ol`jwtYQp6Oy{Ij; zE!A2#^aIvxQ;?uxfMDrHkY^}JX>(+j;e5)!bXuzt{(8QRT&AH~+ zUp?)Q{;_GQr>LsFR(gJ1-}UtY0DSICRpg5=hBO7A`J;imJdJksf|}N`nPGv^Tf5zeAaI!7p}TY*wtv zPoExgjNt(Rr)M-6!FA1f`uadDfHhrMO~l#jfv`J6$cDMPs>mbE(a&>MBxiAfbe^YJ zhKiP)=dw_6t@=FAg47(Mmb51|3p4wf(frXCccCc%{I!;x^Ar&ehA2Yr0V)+pCx8zK z<|ZP?!`D855)r91d*Z?Ij>nM#u^vG&q72V^5N!ejzLtX;xnI2 z9Q`6t08(uV4cR?S$vWegU~ZSu`Ll@vO-QBl*!UTeD_eYr7+0xCJr?~*KXs{gGtfb^ zc^O*7g5a|?5w08`0trzhyBRcnEvAg$zfN&ly%F7_<ffdAM5i1LSeyworfDCv+JC)wcGXiIc$W=wW@rcALU>r80a8dRc z_o+5U2R-mC3F1Xhl@d_dWgNX(msWhEAis}Y5+E&Zdbf>Wz!gveMO&bqQa7??&1zz& zQ*Bb)xcT`hz3c5Y?H>Or5X8+n(D5&bHC^x~;!`ePkmj7nJY%UNSJo4C=TXf_F9HL-(q$N?z3| zcO~C)?t0IZ${n+cN;W*RF_GNw-il&~RrD486hdI>B+uzjA4*GYtw?Do9>+t5x^~?} zGM77_r4Wj;8m&M>886^51NvIwl{0`%iiKH_2~Yr4R1 zCeZw-t)(k049@d(E&e%Wo)r)(Q3eem8vdK&DF=_oLo$l*kQDwOkHcdgfa!rt0tEKd z4*S3XL_p+tSW)YH=P^Lggj7w8h>GRwc; zEgDv2I+(e@GLD0oj{`BN$nXFVE(1Bn`~V;^RT<-`J}r}>l7I{VH@Bwn3DTi*JVGc> zW}bu=fmyPp#jFYdCdt5%J_oV-lS!k+-^4Zu!Coh>mnN!R*z!lA^drp>LTLu2;@OGW&L0UUJf^&c!K$nvsxzvw|Ik z+2WCkn&{qhV(8lP>7!p@4UHrXOsm6ztNuPfvy|tlPdi|75!koZT=piP6E{PgZLmIH zds^uBp7qs%ZFlRQ!e#Q9*-poOyu(dsE}Qmif8$5;e9Fi5nXiv50H67KJ5TwQzxLPU zSAYIr*X1FEmnuvjiIJ*?X8%;6(fJ+;%#@QHy5b=V3m=NKL(UqoagcY2gFRAq{0;J)Ki znA4x3-4D&b2j*J=K2Sb+jcC%L0rr$_BJ*@s>v%lmJUuYr<8jEGA>coTr;VFSCb9>F z+W}k%T-Y2Dv`<7vX^~w(9Vs|taRC_d)Po~}{Dnq)Ejb=X=BanEKr*f17@l^|qU?R% zGmv3e9ss;rtJajF{xeeP9a?D?O;X^`6ntVWO(8V>9s{8S`Vq|V$~`XPew<1H3YQ?@ z)EEc3t%N(sapc+CntZPL?eM|N~m}q1O2;br* z5lz`d%mW4@8-ff3#Vh)=!X3qy$ck74og2e=`dYHJ#EwV0ZDhbe@%1K;M`K2PJPHI@ zL1aLm7`%h^D5!3ydb!DqRtU5**zI>fAk$cogCa6b7~Zu;A=?tlbxlGo3-+J4M>4obFGtXThw|s>J z5*F5^)`ies><|*hw1g><0uj3q*hg76veE`ZkdNParVZ^b3mlZSL}Zw^X=Sey6%wTk0hST{G^IQiOUjhq-K7%T z*x%9ZH;YlJirV<3TB3m-+d8AM^Uv1Wnido&2LVwF;UMgSX5WhLRK$F4_hwFS!Q~jg zv%4(i3AJyI1d`aoE2A4EXu|YJTgP#Bi4CHo)?y0OzSLw>6CX9C)YaUh;EF8PV+Sca zO})hll~#c>2A3y!_ZOs~IVSC1uABfqiWp%wwH5{q@eNPf2^VF*qVp$4D{hL!t}%bo6$qcH_MDdRn^8AYdk*5fAvrOvaYq-4*R&i z|Lem7;4@!e%qjo1$3yIY{%?z|4AUYacfze~1f8C00=Ch;kd*e)%vv4S>;4zkbTcEj zpM|g9yJDs9?Qu3-vh8skZtNSlG0c}PIIHn4Re3camk!41Ry@7tQo&gbbS{Si2zqPLqmfFRCfh{-x<2E|MMKqb-)y3cs}lqxjIlDX{x z2*KK6q;Hm@)<@Ih z04qAs|CJdH#-M<2AyC7@VIYWMSZ7!Y61Fkz(&$g^Gqi(ZZh$i6P?Da61n_IzlGpU- z>ify}Hxayn1T4VIJNK{EUm4WsNy93!o}(RfE-3XJm$mx9<;x8jE%0kspGV6*&%%{I zZAG`L14c0VsTl^`)qlC!9qFzYp~H9gH%MVW#rJ1fa|dnS?VQoM0&AIYWpLkS5DTmJ zo4975&l00?N0CbcauEoEwZ^4ykpyA^DDJ#L`jkZ*6EM)E8X35?MO-lK7j1is1tX=k zWENRa_BaJnzvsz}v#m{d3D*g$F`xp0G|Op>7d^Ml(WNoh`HO*(t1wS}r8Q8N8{3E* zePf|F<7zO6(Tvw=4afOPZc`Z*<#rveHs|;2RJt`*@`8=+Z95F@LQ8iNS3DAf5+%^; z)&^+zeb_A9(cCR6)Ci47@uE4Uycp@V9ph;;T+E>GCf~T%-nkdFx;pQBxx*IM;eY8% zPjdtJit2jb$GKu~hT;6rK)xGIv%Z#X|DX<^#JV&SJD>KnpeY{!z-PWb0D#YYsj588 zME=+RuGpMf5|@y)U8h?LfgpI=4~8O8#S9RydP|~ZzK`hm}3^h^UQ09;T_ke zm$F}(%rgkkOox*rVL~L-8j!~RncJ!K-#g6qA5m4Ik$LygJe2k-nP{tkiCUrA zRXL7F26{u9BsK*%iJ)cBO?KF78!0tQBq!(pEOFDw~WE|mZ$ z41xMwtB+}BAOYZkyAZ6jCV)kOYw(B6Ri@=@#&{ni>W%h^kJs~jiuc){^D~Wr2s%iE zGjAsQ$t0|@4A1~N;DR9{9ypg*JJ~*JxQR5{A`tY$WnzZL-b5_YB98DOh9+i&whHbG zmK#<=E2kb|pZqRYxOx-^9_w^jlnLQp#ioNmBL*Wi5@rbiH6hwonKOePnMq&)&2+U~ zIcB=j_^^_>FORvv92#Su!7CaSGv2$zTI8xC+_MOq zT3}{2H`>%0Y!1A4m5abcm!m5LmZW^w4r?#(Ds$USMZ54%LLtuw-&ZaWr83RmYDmVO znnLAulWl@*toLFU`1W;DcJHXevJ74d7nwual$Y>i+kDb|qswi%SY`33wR91)1m{P& zN@3e#&#l%TM6+-C?oGO{zev%uuoy^Sg6Mw9MVe#ZNnn8@O~A6ghmH3L32i(UC{O4e z2=xgBn$ts=w1a$UzVDyWU!gGoe^eG21-7=;NPJYGEM5RF?JId95kA*Rce3np1cI^O zfF2PRYrV`tW$=crOif@>iy-tse$p>1I*R#amM!^Iyo+7CjR8?x|2RZoGuHRF#aV>i zAExETAOYHMQKotZp!^=#Kr*A2<@GBMK^}69yBA?aVOkc0hGf~LSXH94Hz2`lz7q3K zYlJ79%E@I%bI3qQ2LeRKUc1(O^fR%aLAg#6@+;z0(3OdZOc8yvHP>(b*6$&x^G6!M z&v<D|e@A}p2R{(`U;J6&pk$pH*w_Q7U?9ym)POd-b?Wg_^RuCY zdm5}>d`0c%CiVAwYm5)l4dK*Y^nLpT$0r?5tqrJaFlzt+fB;EEK~y({l-|Cet9hSM zb-g^jV;1&e{JMpLU0(;2t#w#6z=5=mMq|_bH>GkXuLU=Nj8!g{S`Va#@$ov}&KCV$ z#Qnd=a5HFVS=>xs#im7+c8S1&B_O3n#!PIwfHw|5ES8y=2e#A4wd<&;~3!%A)(zbX0&N=M1sxG6amXmIMy)M)1v-s7nqr1F&W@; z8a2=>6}p1}^)+Q}Wy!{b@w*p=di!eq4FWG%@!f&n8*75@-_^lcV&JN;uZ91;sh1JZ zs#kCNeNWqc^JpL+)s>O`si`u((^2l{4S@sJJOgrWP4~ciP11^AY#)jn`Jv8ch%Ptd zcQ*{W-)Itf8+JZ#8!&`DS(0(mhH}GUyl^vrZ~bzBFa~1dqwyfHB|(7%J$U&t z--NO3W3XEvC6GX6;W_9`+p4ings@Z-P_cSvpU0Zsm``3_w$UDvboW?N9o;N?o19Os z(Xi4=;8CtLkL%KcQMB!EvW@{w-nJ(fC$dooK^i-~KCkr_gz`qkSiwdk(<{`Mgs;k^ z*BU5v?ZeBfEx4Mz1lBI1LZ|QBYu7e1udO0*u}rI&oG(I-gmylyv3O6jFt2Z#*^i%Z z=lYqSe#)0$ihg7P_`KH#0Pq>FpZmF=lb`y-e^~z0`L+B{|J?sh%vRvew^x%ebHm+k z4sFLuBKM;5kH42-##ci8H6r!KEN)VCX*i2OG@;q*m#tfc8Sc@K37x(Q;Cl^3gf?cX zx6B zK5$OwJR@(tgeHAuPHVh-p3lfsu-Zz8$@$}P6RC*a7u=p0 z($)aQ~6F1cwMjxj0# zBbYc_rKLf-J+g#YYqd0+kK>VU6Io_x^uf}zslSNIPduW`fJ-XIn>ECZ8(S)*t3&lMjOquD)+$?tDZ? zEvGowTDr{bAk7$kXs6ZZG+-cs1Z}_>y9kpYY(fCmLfBu+pLgqHrRR9~Xli#bX0yoP zv(!ukTZ^B|fTDB`=RdO0GO|?S0E=vKP<%z^u_&m*NPJ=eQkgvQY%l7(kc9=&pY1)I zZ?z)#i_b>pij}WdQyHN1n3p;t^Fg28W{cF;V8achc;}Q7n86?z z49b%HtqxFHQ>d?iFC1Ep0ztOOW_JXqodK!11T(FXrBp>#PZj;~^Xnh4U;A7CseJu) z@At>`{a+sdz-PSv@?ZYT^7sBfe_wy_Cx1fzC;zK|M@($&=HTf!mse05>twy9scD;V z3o>;FC0b(BRE-jqVv?sKWGHq$I_&a)zSxt}+kLI$_Dwc9lbyuG|1VBoMXGLodeOow z=y3h|0!7)z-4Ay#ZIVLJ9jMD%S)7?kI+Nf_Xu@4^@tM#x4_Eom+56jG+p;W840^^~ z``jCm5&2$GnUz^@?#ixe+wF4O?xx*tw`~~&Sms5t5E2r6K!Ah<0=}BRz&9j*0Dl61 za3g_)1d=gW-L`RegS+c3t1>GyGBP6Wz31$`<{W&Ou}+1#J%2MuUFsq zk;?2W=ZfLh8BvTrbc~@h`CwNEH1^)Zit9p|Cx`MJ2HUpHEck;;4C)AB$b~Hdpk~ok zwc7S#9s*X|-n0JuXHdjXr=O80hr3EPPyjNhdf7CF)r_+^Pu!IX|RGGM-HF;UOTb^wK!{G zK~7VR;?77E4UshE3AY$ih}s&;ET3PQ@qATBRt|rbz5h5P$4}E@y&U&N5sWuFTj6h9 zDkr5iyC$)BVm)bEi>FstdX5RNJKM{QB%~|7&|as6e{lYu$voNWnr`A1MDPj$Zu$4* zqFIu30Y%)yfE6hbH`ScFZSwK||yibHYXvCi7&%>TrSDUV@n|E>z z;S5?4`cN@T1{aoh1zYoQ3U_897k$=J%Q#3DjWtziRyAxZUh(?(idf24!@Hq zR3^-VB=2}DrKDXvDT;CrK$eG3SK4t6;y{mu_-cTY9{X45?0iYxFHd7u8{7-D4H4Wn z-SErb`E6|53p`(6?DY%)e1X^XdPTn}^wSUUzyCM?CE52OGjymaGvXC-4E9PJt}ss| z6R8Fc4cu*Ql>op)8+;Wev)~eLdWjex#d5=ZNQ@zIyk5Phjw_YK^Byf`w=(L2Ki)$C zn2vORoO9*IJIX)J51Y+a!H;!|!#U>kr-MMmBcfBfQ!(rK(4cuB`j!J>vs&}l>lN4Q z({$x`Wf7DqA3*vEW~Tqq$6SdbXaZa|bAyli?rDXEC>~y1X4d@cOaj=N=up+M&rWXJ z7VCBlRW)gGkjrIj4VTL$lNY*jCo?hLv;s3!m1X4Ex4E^3{kBiCG{NXoJ%|tlD$$o{ zJ6om^9tN9P$A`Jfk3Q?nuU-fdMQyJ6&@9_1K*YuGrg+Zrx*(l^O#o!B#0G%j2?m>e zPkTcUppD3MpWoVM!l8pDDNM9YNMNuq4@>9>j00E}0^QMsJo5xNGq8Jv6nbCGoK?PL zn76&{B^N~{JWxdHe2aN?3=5&~WPUSr!c+_sKt0J>xZCb>%6T7&WjNS)iq(?&RBiQS z%nVG(aUHO7bJrsZ&I=jvSYEU!Lv-5sbCzSy)Q|`@U?^IBt7OUr8k<8V_1HR2ta6KV zBt#o@R)+ik>DlIW-j51}oUEty@YbN|N?Y24E$1P^UfA0V=WXSCs%ILnImEs6cH+0L z`nd1H;D|IAaIqr$9k^nDziCq(WLH8mcAT> zr9DuDzq$@ePwM_W*?wAoWqy1_ zVS)=Uf^_LRN zt^^QO9oTS__lAD>r$0sSJD#sE_Id^YzQ9YTDG|5d{{#8)KmR+Tf&rj|fboMHy@u#s z8AHW1al#(zB?V*PFX%nxlgcZN0UVHby~7mB@m$funh>SFll6#T@WDWiXyoa;&xEDr zv7 zYl??Eyl7bdAqeop6Tr>#b&{^(swJ6Uyi*n0GvT1m{cd>!+LopE#<1-Ew>|*~v+@go zhs&gYgcFbw&z=S@aC$%7!tG;dR{qX2>$|eebZXMjFzK21TG>9URNxDx_YRG+hUrf` zUH?afen3qSqjfuhmzLIK7obN93DGz6M35%vCV-12o@AlIC%s7v&3EXf(O-M{*Z`??=4{o8VrTYMR}XdJ64Y4^gDLY^@!DllY3KnmjFatS-w8re|{YW zPl-w4d7+?~le6kb`EVccW6$B88S6iJWF?bS7=+Ku03`}d&fa_sttfZjA&1jGZi;n& z_CD?u$NG=>nhesUm&#!k=ekfzZbzYK_WUgBIzoA*EsO!Gd}B)QWFhW_MDmJSwE?!! zs)wjiXhLeh6`bIC@xBejzR1r<)c*#0x}{5crFf*$bpZD3&k#XB0;sXaDA?LzTh%Y9(O+81HS^@kECCcxbmSy9B96X zAnEZwzY9lpy}0<5#|?AKX6G>o@*C&>dYB zPpR|@9T5H*5J#vmvabF}Wln)8t|DRHb1uN3pmc)TRuLM?L2+nhxf1DhrXnJz_lFq0 zn01&rA0(VReOSAg7NC>+3588%$ccp@*f|uyzssG^a~6`MizS8e9-~xi>7wj$&?SkH z{ob$BReakvL{9aQ<<1nadhD1=Er52p;Niss057E{85UqJ@ZFNNZI>BRaJ%hspnLZH zP|&+8rzTs^G{n;y@5IW33h0#HN(zt$3bkc~^( zg8@2|-H9o!Hr;(VKp-ZBYVm?lO1a?l2qjx{v-Z8C?;Royp4s2;3v0iQssGc)@WWcD zY-lDlG}<&z#FcLN?ocsf_pM@r-RJY}TZeNTxS;BJCdP0eA-38)R%1#lH)(gpH*#NdGc~8D^bkReg1?5 z>(6e_$x;nV6~s7-$VYHsRnjLRmwCk7uByIgf0j%FlW&LAoxqLcAB9?MhN|>-{(1m9 z=pTbx7Oz^weG=*G?90+>9-{GNSYH>q$9oGZl|K_g_2YwLuw^|*W*-$?YA>boRq>12 z6#y0rwN*u) zl=C%~AH(y@fcQoBP2nI0m1vB7kO^im$>Z!MO>`#ib1*{6{^EALfugLR?iRI!AwSGa z84O@(o;5!1vgWF%B8UY<7y#>%Gna(rm5>&Hv+~T_gR84JsN{WQ zM~bR@Q%K0NZ`10W>ov>2#v~u~d){OY9K|?U4lI+yU~J`&o>`Qo=!5r9@Jm`V#MrVQ z0x7SaxqHOs;LxKY0~>F0SxocOI`FbY3T;>{&wWbmtW6#D1YRkEF}mKy_2)mn;yL>N z%ekHbfG_4!0QkWVexP6d_P6lvum2Xl`r$Y6(DVt=R{)%&3srYXcD?%OmP>M5fwu6$ zwX_N+Y$5PwxVwkLXF(A>bN$gmRC;#&i`_s^?mM)?!CiF6sS!)?7CO~;td=r)YbX9J z7bjk?RrDM-LUUBK+HOA?)pBHZgp+jHmwlmvVC*K)P>{L(EGscc8ib%&ek62v{{fn zBfq0ZsVL5VIGrC}Ua&n}aJgI%B?e`NO>_e{R2AF9WOujQKA&wefsUT_jxTL1{ZXCS zr_(JSGs}PP*!!LUfC;=~Xy!t24-8q{^jaoso1PX~vMi(!tZGY9(wzhR8RnVpH`58_8V)u;>eWvoK#5f)N(6W1S!8?={G%- z2a~ZJ5}2N~vjCPSeMnlZ+nNjcp#T$bK<*x&K2_8`Z0nq%9+I8n^fsT}yWdyp$6(HG zn-s7HDB^Dx$~O*z^9aJzC?Hc{cBe(1#9)QX;NH>R)%$PB6q?7LAluR<$RT zW@D|(=_;KHS)RvvPnKEp6+86U$Dl#6mL?bxPB``-Ez94>GSGvkomgd~QySB@TDNnO z>N*=`A}E%bHA%_8)pY8-S4mMMph3JsNkf$%H1+=IHUlNF+yNs!fr|XaoJdN)7c#)l ziBk}Zfl3MwVT=D^m3nGT`l#4n7y;mWz`yIs{2p(vYmoT3z#Vle07+O%Yf5WsR3TR~GW}lV}g~z&UCA@W|836d=FMav`*Y$7z zwSNVQaRuEOQBBgxCheB8Z=r*=CPPRl0@D7-8Cbm`1xM~V%d0-Ia zbYYkUr2*tJZSK@M-S*q`$EH3{3$`aZQ+TO74Mltm#r3*Rg?OJC`1?K+;K?>E|9zi| zzw>QX%<_4d#*(&OtRK^wtvVv{oUaFew_2IL8MEEYGC%v<`&7tVYv{v04TgaO?ru)P zrH|8d5OhUD3!p=q;C9{7Z#(wBW4G{zn8GI5&2z!&=L#$^-^F0Wa5sC~ADuvyG4gQi zmPx*ClAa3YFFP>;p(IvQ*l`bwaCOymM|a>w-77$?s`VI=%RmJIXh72&#Fc7K4#^h& zS<=vrMp^ee5?~n(hIT~Af;woD9tT`DZzenxY;8*mlZSQ;z{3`Njc%}eT9fBx%PVM^E01L zeH;HE>k|pO^RXlFux6w7=F`2kwow_>9fXu++Y=Y_BRsqE;S(O_d{~OgpDhyGP6dDk zLl&-?R-VPAQd#L7(uHLo=naj%lr&^uwoF@~QF79!q8N}U9!uzLGON)@LYY`Zc~WL; z*S-HMWv!YEYK(l+nc67AyCjfl8WtU+;IaYkmE_(~&T~D}y@!^u>NWs~W~hMMyn`(T z-Dz9k0gVvkjGpm?J`q;h69v;gp#Nw1Yi(@6@H%O27wF*33$JnBFq0P z;{gVluC;5>G{Y6LpFSY>MhH6i`qdKDZ~}O_D(g^YM8MDz)G`m?vHDr-fJMAFTaVE& z?HrSU#M&!)C59`n7FI|!FL@g%VR7PAj6S&ZJ42Fca!cch4zYK3ul2OuJ&)w)w7o-zVvtpjm=M?K2eM z^6(HA;%%Gt=O+k|>mW1q+u=@!WFLJVoUKJodlz(kj)tn$u?N+(2KsHcaD)*h87G~^ z=A~PPf3xUp+x$mXy8w91dJ~==4qEh{Kte2fi;;(-_nlCBE*&=#fHcc~#M@gaM%i&9 z0SftRTxs35HuD{X87H3^)|V3V-)yeJO8}NH!S+Z9oG^NuVnBWI@i++LYdM5%Nr5a} zE~Spg_q0ovt5TZ$L*u@}_5`G%B*4MkB*k(5r-wVvI8cI~%jd*#YNr>WZ5iu&7xnt= z1x1n04v|8B=vh^C&%D`=paV`@DSw`HkRA2UZdsMZrB$){6&{pA0B%5$zgb^7?d4fW z-1)gI^g~X+a$25wh?RjDDC)0DCB&bZN>_Hp+vVQbXA4N+3VT`qZ0Yy2!##Hb+?IlI zWtA7?ccFk5Y^`ed<)fpzIS{c_PFI(c`{U>%>*_)lQjdhek{Vo}alm-B*+fdFW_K@*xpaHnBO&yF#L ztntZLuJWXhGm$ZC6wh7&pZoO;0DQjJfla~|_Wt;YH+|nB?Tt9-%mx_}I3}t*;peaf z)Kl=Ys`y+v1G78PYnBu_y7pBgZn5tIG@(ll0g|DzolE6_s8V)V3WF@!zvpUK^ zD$%&ZMCJH~d{_y(6T zIn+Z9wkJM@uM{Q!pj8Axj@)(qP6B*c zd0@6q{MFN%$B9>5Cl=Zng(XKT@EGJH*g;@{h?%z3iUQDkJnB1oc2vwGpCH(tIV<-j zofUP=H7sML8J%(-s-nqnLb3HwpRf|sSr9AEW3X*~N4FE~ntg23B5!;?J8pdub3pbZ zP4jmiy4bi=krs%~>K+3bXJ6F=^DI!OGtFP|5)c4wdOdj8RKW5}3Rww$>ar(U@?eE6 z98O{dXpq2Vh;>y&zf7ZyJ?s1=a7L1;C~07NT;v%(+s`==nvT3rBC;rr`E+T&1ahJHhL22>UDDRq;kub#hf!GJUf$z(+@yeD=mTbs=5uIVz(BF2LBo*UnzVvC{_E zw#x53ij{rId0VO{EwUVFOv;p#b62E$f8^h3tv?t2zK|57YJfcD+sux&pFIS9(9k$LO-}yPZlRW*rgF z60i2o?uxj173dhLSPC%u6d<>)VeCCDuoF12TJ*bB8yExqHi`i6-VXsFc+xwo=YF|t zA?S#WbtdTR=2ky4VV`*&j6Ok#p_!TAUGe*EpL1ovMc>^EU;-}bD(^1t2w=tg=L#$< zXJW|#_q~UeX&#vN`^b#?a|)Xsm%~u-++6#9!`}Bei2eQIKn6Sy1Y6sH@Jdjf$p(>v z^6a1I9WaZ8wSi3_;o3j_UR4!syTJ5>P{>p9pbcP>@~|-`eax%FT}ydgYXU#4z;w-g z6$OZ)o#|PYUBHS`nF{i3NC7CUJMm6pHS3Nh=9t3uVgo-@jwKbvu*4Jre7Ur~S%`xi zEzb+zBw(*}sZ7XcuxV=N_{H1be;^c{Vb#&}{8D;>+bb){n|Gh5g9QsMWctsTQ`TP$8Ko==3*PUWS z_>Rj=l`jf1KM&6*577Y@Sv@>xO}RH^Egx71ahLqL`V#yi`L2c_9l2zd%1mCqUaEzf zr;JuYzI%J0)tg~A`xEy0S$sTaO%4xLN2Skbi8e}Mm5WEiy-wKPiA@twk9Dw3*=+P{ z?=QOG>wo%Vw7>gz^{f}q*XMUV0{~zArDF^{vVwVgk!W!^vo!(BmiE9}hdp31YTmgH?7wN4A^r`O=%?J^kW~X;d}o&)N zSYOR7kKt}lG+|4>i@#BMAe1}$+sa!u7CsH2@f;2wE)!sxG_-cHf7@X{>mtld8>R6? z5Kq%1pIEucar4XZedX@e63Lb%q5Jdu!h?dY^o~{gVVX<*Jgr}DMRuxI6I0>b6lQV8 zBv}GJt@Og@Ma=Pf%P*#g=YQ?syl)yi|C~XR^d`|2Sjc%H8vp6Ns9oWjbFKVPT)=K?3EO&LU-q81#B31yJihi=ROV{+TC0QfZ`8urR=6 zhI6qZn_1`8qxRT*;xT81DH)q>KG*CUqsSkY~%-eMvl6rpQ6WGLMV& z5znLo*mbT}ac?*RM2fc%kCTAbTxD9g~ zJa-~FbmQcYbu9+yMDwQvDEW;A=mF8pelS-o7Wj1?^VXrYo&8A}x59_@s*p(VYhNzO z)94@W?BR2X5N~e<^iPyO?NkKp0mqkDu|;GQ`p=hJd^u*OtoMA)@S^VLGYGwAib()?}Cx-}|f{AgzUPsjAp6o0Z*@EFWhFpIgGWty!ihQ|K(2+i*qD z1s+Q@H^vBo%B{N-h}g{Iz^vC6-%oZ4OBQE|16!N!iXl*CDWxcHcWLG^u;)zbv_!ig z6y^BrY9bNq@16$U?o7bviu?2=@Ob_SmTU$X>?VX|J?NRhv049n$}`&pdAcd-K+-DD zY?thimRL8Z!RLa6K2jLOsw)KWH#GW_0;?rDG*1V&igoK3SQxW*!t(FiLjfvYQIuQW zgI?CLWaqY7_Rr>?$o4tWq3_k;G?Zk@xlkxFO7D&P{cUk~k?nb$`SH;L?U~n8h^=Dg zA;Fak%Cg?${9=8>8Mis(k?TE`?z-H!vc{v*USZkFpQ-1k@vEe`o_%-9Ky96>0vwic zK&a1nuX0zhKDdbMl@6z8#3}r9^6IJ##!6Xq6iOTqUT16+B-B*}_APYJD^nFjD22;G zK?(2cK_$=qQh)^S0|Ah0zYAVdI605)2)ITv-;*`D^opL><(spsCMT>-%iX7n!hOQ2 z*S`)NtT4Vp$eg#bB2H+s($;qf@iQ~D>=4|xcfO8~x0e|2zQZp&9&pdspZoO;0DKA8 z`|rPx-}uen!2aoN`dVAbUjIJNoGOMe(#cm?TkZ-eD1yTx>XomHkjop^(SPrUg5uOZ zTjAxCG3^FtU0p0?@$xev6M3*Dg%GGan=cN>SS3$*RxBLRn_<@N3KC~I%{ZybtRvnv zo>}IOTQ267O0{XkpYHf`*K+LtOgOiBP5`)Dyv`(cy<6$I7D9(9_MF@;PRh*{swpu@ z--nDomEUQB_G$nf0w0(KZ83+)Kfh+)$wR`F?j_pK+{{Zq3y~``g{} zGh@A4+a|Qg{N24U+!_=av!z+cfh&xNZ2Pml5mf=^5~*?FdBC=KzYB4`ha5ZzS9wf# z{0We>5E40-It5x=vA(@J0S~@f%?rgJ64Yu4ywId2_~0PL%%vca3qi!d5EtBo6|kp{ z0X*jMFmoVi5QSr+eZ9_c+&})ja5>_I2R8mrW5?Gl^{dW<)R?XIhPCe(K#kd_W zUtNInk19(qn!Fy*kN|ZhZlF&WfMK-+-5FPS7?AsJ04sNa+u-p*@?uS^BbqDB7VEup zu{s9OITA_)4m|(lgXP~z_hor}p>XV>_4Bg+*XI_^Dt<^}8Kqke{w4|875`wBj(-+9 zHIg1fM}!q|Yq~eJX@L-?o&gx~qF^mw4x4-2SlG z9KqE`q6c7riyr^QEfV)|vc}94kn^YfPRaR;&Um<6wmfs*oNcSSj4y~t%U7HV@wc8m z$Xj&p%!B{y)i&+7iGAg#RB*hCxY!Vy3@C+D+HPi>cZ$k>^vMKJ}YOPTK`&Up9)F6gOaWw z4;`;!Z!!1Ouf`Nuhd~T@;)NInCj9%Mj z;=M6;09ma8gB2)uIS15g&d>Sr%6cd==dMFFhVTKd}=g-9fA_4$tB4L;X%+-b92XEDKonKYE`u~wzTXHXHI zH?GLL_nSKw39L$vfpTJMnvfj83fQ7H*Xb-XL8s^tthtkX)r4B<{jqt;k!IUQuF^uG zCoLrR0LudUOAvivJg%5=Lnt_b}Ym+eIL;| zNVBnqh3Zkbs{HPx$3n76+|X!oCe>*k7W(sEW_QkcM@8A7vug5RHdXmez2gNfG@Ht-39spm$r8@BD)hF2BebW7Q?b`Ry zB?H?JpMa{I9vfS~DfW?kE|gre3?MhR|F5w7l`EVILW~O1JBtN{V9itM%7BEB#hsZ- z5sFu9R0spy(b7I!Aeb2+(cU8-y9mAUBn#uI2);!Z2ymea<4GH?D!{l&4n<7i?IBbR za?5tAjGnR3Zv7paaoW9i+-^4jiqRngDc$Cs;h$Oi(zcgm1O$#aw*F$^-6JLeI(6_i1Ur zTrMc0y?8x(1Z~114pWe2DzW>%V`uO|HA?_W!`3p{poqurnpW6GpVC?WU@aCmTAp^60_|LuRcJcZU|$5X9(wY=;QKvn_rmX+(#VH zAp|bg-YA*uLYVxgIr>$Uwlhuo z$Wh|QL5Zf{nP1&&FTJ`>EL)yVd$q2UfO*J=qRDELLzdHmC=?V+p}&0W;9k0NNgfh$ zJlq$FLl8HFN8(t;@}AkYe@y*4qZ^%52hq8bB0eY)enRKi>A9ZgpO(4SSgR+g}yjFNU5^%tZwi0twSL)S41)sBgUsKR~KpEiY4enha@O{dABE5pj zS|@RcW7!d4Wb%SgjG;lpqKg-WefHH8C8)0j(qZ{Iz{jIOdrxi}(>82oTu5MfPS^b* zaqF&8ABvE0UzSG*^7A`!%cy7O?zGlGPpJWoEGB4RzSIK!dM zShdDm7BE+6=biE5P|~S!rg`iMzKg;qmcHNBwl(*|!R>!(wT_Rx@K_K%Aa7y)`z>f)pPe4I!CkJV%vMi)6*6E z=(=s&R2Mgq)8|g!?%QQ1k{f*{$BW^Hyr<~< zo|*OcVIaYXZ1>P<;TMtgH1H&pI(JC#W4_xG4+0~S_O)%ZdDR3Ej4s=@K|3qQ4oJ;? zx8HioS|j^BT=}#CNo(fSFkSXt2$43e@?1xn0jn{3#*sp)Is~9$kw<_gp8PMv257O# zjJ@631TLy|Q@P^QTpwcgEml%VD7TSC(n+`eH&ZaT*05b#%$H}p4k5&puHHw`CWM&s zw~f)(f{u}Z0o?ngg)k?!=F~tO&o?oZ|E++9s))P{zGhgcOi(5jN*_Zi(siGgz3rRU z?=0513LvIX$c(TuK3@m-;>cM*OhuHxFLL41M;FJ=nCHXY^%%aM^9 zM4h(UZ(m7T$)u!KDo=ReKhck)T%@76Ry}07(Unrl0~5-x^L)U<3Vghonk^Me;bEqt zM@p$4x{=;XzD7KsjWSKvZl5xQvZ~^@LC+r5vD{Xn!ZWZM>ri63l+iGFz@l2wY2CZv zj>H?ysGa@B(-$@(5UVs(`&W0h&4da)bK=RC-_G_zqg^NHxK^axvkdmmb!Nop(3vAV zd8f@2@XF`!o)UlhDQ%Vll=6Cuey=>E?zQwpXbRxfzc&mjO`!u8%)74rBKJbZZRL-p0(_ggYeahMWi?emUWIY4k*RvzZxS;M@#XRW@D zVhX~UfLFoZss>w*t`HdV#)DvYxY)hU1)t&;yNopi^95L7YdLpZ`FT>NXTKS@Oa%5w zNwO-~^Xcx$f`%W9RS-KRADamSMqk7mtK;kZ>iR)@(ud%+88XwAJFt!uoCj7eU|ELSd)XxOoQs04yx--Yg)^l*p;T z_5gXj>|x_d2K&rkFf7L3pA%P}G%f4CR&?~xt#pjo*PKH_#1mzC!a6V6)kE1O%p}*B z$MLhE6^FikriH2oBcsi0DBfet>I0@Q&)g3ZmZ@!PfMy$!@4#NsY|fiV z0y(@lRjo?EY-8ITR8q3wD+z=W6$w1|MB5q~S^zBpELdpFBv@3A4yDZbKno>J!eZt0 zPs^8+=T6C~kb5=3wwMkr{EJ`-6jOO=F8OO^^RD>Sn0g%k_D-|^sKnkQ+iDrp-S8;~ z>iqZU%i1@7;2u;OJyAd<@sLAEfrdgEbPQsNOW(mO&pHhA7(i**mc1-vF<&yCFX|A_ zAk8+Z6iW}-qk;a8spr{JzNab?tO%@XMqOtvjFXoR0a0pNor8;Wn~VS!YriFc0qCUk zJS*pVDq@4c@}lHG)D*A5^P)d4zwF)XhCe0E`W+Y_L9!j~Tv|E-F%|nur)@0H&=3A0y zmEa9}uJ*yR_Ol{SL&;+;C{`Cb;4a{<6Y~z8I7yyK;ejE(p6HE|z>Tyj;bm;BCVdvq zqX;cfP?%V*r$dl~g;^BE*Vd8)Sz>&&u-8H)ls$3ap^|49KX~MfWs#j4?RdX9o}(+j z;`>K!|BSuggMuS;jo=FoDduw|2p2IICT-|UGo7$1(1^Aw0u^~RM!$adn~H}QH~oxm zJzt;0_3Q!gC0t({1K<70hClrWf6yWL7?3A(clDe`0AecvAm;K`Bp0cTFGB0vGn5`6 zy0dS}+nhBx!m4yo!28W8?cr(u$7gq(7C2*-EHJJi0f$lD8FRh&UI4(I-`w!eTH~M4Gg16Ezq5J_fpund#q>{OOoNLED;@PRm4aPST+Lv~Zi@ zM|4a{P{lGeZI?~QQ0%*Bc}G~CXDPcGw-#=Y-CZFS{np{&)82RNw_8eN3T#^{W7Cb^ z6v8H9QC6K<`L}JO>H@abyoBBWJf1!P8p|OtUD{^`{n0yyc2nftD_|zP^HO|V*WSYx zYG!o7<;50?e*$$g2ZQ!*UJT?qZY~3JUaZ92VsZ?1Ey52njHB6{h7S;fV#03h>^5B0 z6JJrT5YO^{GbVp%KY)8a)<~eJ8h{5z@{`dOT{~2JG~fcQCxHyboxn!{Vs2uUxBNbo ze=tu#wbD1Myg(plxuVRO`Z}k0Mr5+UV!3w^gx{L7wa-cY7N&S<`8&8HVF)49hY;-Z zNNY?{_rJ0jM%P_dUc_1AF9?3h*$(vZC@ZRqrdJwbL3TYa2&S5I=1(0bN>KI~k5vx+ zNGoc5ugCY_0UknHEyB7a6?l(v0x)ioLz(2{6>j`}%L#yxnSD&MSW9V(SGsd=jL%42 zFX?_SQS#644ZwSHdHC5?RX-AH%!m48mnhFuzTEX-B*w$ z=1aHndYDJn7k+#SIhFYGL^P{WAo!Vz0@osg|DtFB{#gB8} zxRdLsZY%dbgvms8^R4&nCPLr40C{J?i||~>$A=`nJGyoc(zpjn)bII&yP(V|NV7r~ zbr$H2#wu2~#pl_RSu68NI&mkyvkZoyqgYh^+ex#ol`=Awx~M7WCdlp|Wl6 zcsrNfw$1qa-g}N$0cq3GH)8gQ=7CP{)1p2VZ3KGnuHsq0-Go8Qz@LipY`2er+iizu zefMWBmo59`8pg;q*UWky2fQi2O&a<>!iwxB4tFtcO#*ZXy6qNzL7o&YnE1RzctR7j zZA;hk=2n(g+}P9DX661Q_wrE6SrCAim_=T~r9G_PwA~Vznm-2}@TqKBwS%H;W`7Wh zh40B`?(-V)_rt*)OSCt`PYN?2OV0Edh~$xO*>5uQ2XOKz16R}qR~k&nt+rm2-UCxW z=9=$GjQEkn@eKcL%~*H&DhBd(u*&f-#1iDrbD`KszBASn^W%g=i( zs~V>?!^}nFx3>teV(TN{$=xBe0YGNm3bZEBoTJyl3GKtBex0E_ND|25uTF&P)GWjW zQ!Slb_Cxt~Sq+jcEo4=hJEqc7R={Px%l(CA<T3g;F)t9Kt6 z+BtnB-4kb%+W8SciQl|;09 zEP&KWa?H{nAXECGk}6-L^_8?VC+U-s3(yt%>ErFw@oIE=yj>1((DU{AU(W!*mvOy# z0eo{)1E@bilUE|GvK~{ylD9ALBOTK9($kMu;bFE3utmY%bGMCpxGW5VkxTyl&;)}4 z`P-Sw%=bC}IJ$h+mg+&mJr{;5uGd?X%9}udB@*;p2?Pus^K)p% zyh~P47~R`60pDEOEepYP>$erp@Af2*6G+fV=m-;DIqyQCG)4k1D+ov`*Q7EIT)jbzqmJp>F2h-n>6g6J7LWD``C9yZL%0&n~%-G0$YP)kkf2WF>)Ay z1q(R|APf%dG^osEfKz}UvFF^2r0_MDlLqbEVjUtxC1=c(t%JKHtpi$+ZBAG$4hF6cKyDuy)gej%ju0 zBb*g)0zVqjC_FpJ6CrpV^>fmRGW};+0stT|L=hUT+Rj$BP=TaRB)JVwG7#;uf3vbl zg=`p6P!ia9|DRfXVoeLtj(`PC{3E)+o-EP7K=Cf#&-pYuG%8Z&h{3O1#kP+;f5Wk3{%&bahCn|VUG}eD zeg!YLublGi`<1<(uRo9LIS0U(b$$9{VEm!z?Uz3h!N)UMSOBrHaTdU|0HQ@tk8_T{ z7{~YCv+#!{G#RO=gl(jv-)3D%e?J7m$u3}bu8c84wD}8Nr1T}Sm?G5bq2-7#!5j2W z|F4#&m)nkqPM+X*$94}t;l?A0ik;HgAD+nRMm3bv>`gc6VX^#U?=n=?vR%iy)J&Om zDZn$~_kA}dU2whL+?`rou#?>cM2n+yEBF)&T?zA8F3Zf{J0ODH`sC}WL4ZxBd%Pjz zGuFF>U^%B?D$wrD(BQGDN{ImNSxf<^-_%OO!N3GSq+#zJx7$9g!rjSfWbs&k=&X3x z(#=#ba}!K!nhAHv`1=Vmi~t#W_l)!&rs25(IDWl_fYB{ygDc*%aS}TCFcptJY!I3K z^{|f_yY8Zk)6n;%M!clILNv$e>$Amz6U;aYiMUBuf?Ja;lQ(9&tu?*>2qE060>2peB zk`r#rym~osm}gbKHeZwxEtN>htW#=B!gt$^dg#>h=pxgq9dsoF!g?kiulTkCDYA4k zUz#A2)Uz=^R!f8vI!8%Phl(+gew3Yd0N8l--6NWQvLeAsW}eMq%!TLbmdOg&b1A0C4A$zUx|E)`jPG7!EY1yT&|2);_vt z)$%zV*~>35K7Gs~QYVXy7Yw|}vL?JFqHms@T>9F*Gd za4`1bkeM)73U4fXRVg@RkzaY4xd2)k0aGQGUO8iY?+Pu)C|7u#_tLgfj|%9h^j@b! zDa5y&0|g#(a@0G@T;v&c#=6BP#FxG61Y)^-1YMiFf?oD_zdrQEJCS~}cI5f`T(4&j zfG_7#>^ES4z|j6$fluyYqQ%e5Hqerx`E;i&prnur87|RxTd>%~;)1{!;fjsEb);bC zxqM|m2lP%$wU29cYj2-W$~Z&iZr6keBdc`3(nHrz>M=CN7=5jeu_Bam>Z zn`e{P?o|y%wVSd_%soyWHO0O6={E1~m>yT}it)BhH}1`9ua7>W%-wD!^4#TezxB+@ zFSDj@A7%+3M0c~q_PvJ%dEV2l-n@;iyVW->(!g{{9ivkw`KkE!KCs{R5I%OR7|^!O z64uFd(cgzvN$|vTEiGPSgeB~H-8062Zj5PtABwG+E50l2HntFOEu3NW9shJsQ5q{E2pVr&vrqw3!*%8W*eq8?GrBvSy@G_hi90wE;}|?< zq#^(b4~N{ZYCt7YtS}+iOEfaLS!EC2o5+2U?4wvBnTF1-ya(ZG8+?lMIx}Y=J{vsh zz?GJ`)fB)JKh7Inmd5f9CP@bBuXoUzDA!VcE3t~h^VUNq8;udc2T^WFa4fBX<@_S%C}><2o9Vc*6730vs;L&zWZ~ zuV)6-&S-n~ca`N4a^;bbH4MT}n1l3h>s0k5VJ@*QH@nE_Pp_|^bbR>77$1IgeB{~s z|G8h!9spm`^`q;CPoLhAuk~>SwogQalkkh@1^26LI7vJjbT~#N>4_B~{v=>vjVj3Z zxS(EOQ21M;FODe28R!ac9Z89OEetZPz+&0D?=p^4cQH_Z-hxBYe-Co7W`^~XU$C}3P3H99YqXJi#p8hx>NRh zJ4iND;yg}!zYVj(n=(H#PTsRet7X@Z3Ul_ZcA4G=Z4>Ob9zw7Vw@z3xK}clU2PQzU zZJUiJ-{VyRB2L!9fz1=dNqQs1I*WvfbNB2WB02$$HU*-6SF2fXLTn$nS)~N0omktK zhf9ew_p*d&0^I@CK9Ot%`o8B1@jYOBC!T|ZhXW@ZH z>Y6T&E*LKsu5hzA zW?wxsJpn7OR2x{qzen{7G-#9XpzyJJ2+3v@BF5;`Q-j@a;}uC02OMj%H}fRY>51hP z0>Ua}?y)&r**F`1Y7A(-YGO&ZU)hQ#z_RA)2i-4pDlo70{-KV=zh#$xa;+uCZS&*{ z#-Cr5^1>FE=lNjbTgsn8J1v)|NQ-^umZzcsG5>SrhgtifZ}}DTNhW9}m|Nzm96%Cu zKG7lgzF<~bZm80HROUH<*{K)W`dI)rFa--~-A^E3(kRN>dw(1t@V0UTR;9F*^VCEC zf-kX76pBK=KWV0M4_l922OC8}mU4gDzcly?@%bjy!~2461&q%DiLJR9nKI4kbl0Rw z8AH$@=XO>?&`f`!GzJ>C^0?`ZMbYvNzsO)bf3DOg$Y@IUp!bLbC=kv0>z7h_P>tL^ zE34oxH|mlr?yQU+#^^lK)~xtGE<986eFU1X7WWE;3H@aLDupd(ILH8mbJjr62wJdy zP11|!6?DLn@;KAFZpZScRno0Mhhh+jafD0x)5#-odezTmX@Tc^f%sy67SJ`oo1;X{ z@;!?eCtb7YpZs2&S2(Q!V2siC{&?Nr=^s5__458MUpefC=j-#no;?7*_)7rbEAM_= zf903|3SK`xT_Je2nsotE6Q%sc&6I7b=da4&*Z8rMcvjn;Q*jGxhI8q)EtG?anM1W) zoGIu^eORSAy6f;^r9&QM3w>PJIYCqpGT!>>fupV9!D+glI2n>8X9CA66y{$POBOE_ zxZ;t@rL?pH$B8rm5fQcc?$)-cNQK+gOz0PG*qBPKm?gMvX)Sgu{YrTgE zxb)bXV6%j8A~TuY6ktb;-}^hv3*vIQKs_lTmD;=AyhgoE4hhJ!^jsswH(kePjK0~= ziiI$9RJg+G)?wH@wbm?_RskzX$nMXqzndd5o*@T9Pf=E!fQ zJf{J;X4C8Waz`4I+Pb{;=Cv(7xlL>(OYH5lyQ;^Qgms-gSn7P7b-aSPu`cci*R!n( zO0OXp1)}i2L;;L-&Q9p7^sxOs6_8M0LxvP+%`O$vQ7I=5qu9Lh*+%LmD-daz)sV2M9}a`)8#!vB#K-Gl62w( zX=Pq%j8Q@rNe_RQ5*_BjOY@^VcRmLr&d5SAyRA8WmpLv#$hrT(S460aNIk?)4)=1R``m&E?)mH5F|!MH*(^ zO&9lVoAlYc#+n|M1V{zg_Z=?4`N7!7z}BWkczQD^`aWU`xdfqa39nADj|_`Yi-F&^ zSyoU@fhsZO=9WIkJTSt=KG(yrN&>@@Gfppw6jH+t)&z=hFAT+E_S3x|+@Kz57t6Xa z;Ao_x7vTyXf)GkKV(#WxMuH^d*p_nb*0Y~ zd)P{7CU$Zy;cYSQlQGo!f<0d6G3JLxH?5Bej%7}a<8|VbRw?D6?%}pNf3fzLc7iH& zP^l|XaB#02O>|!sDvAOcyig-|2``se-MXyuF)1?jT}!ZM^oqoCu0$t#Q4|mlFOcBH zzIV6}YKaGjuYyS@FX2fFtWw7#*SwnIKg^!0uKc@lE^*%k^o-Y0X?lyrI~Yt+r0*fGj=B_8$xA>jG%Yif z*m|J0;k4uSXkI|`?t1wO761uUA2!*d_{V-qd9`?^5Gce=#JxCP#Y-@HB;k>{3PYV zr;gSO$+Q|E9^%i`Yy{GK@B7fl{o&(&YnL_G?DO>nUC#i(=Xe#caJjyvieDcC7m-~A zPtt@lURE|L7L8vl4|y`a9X4Yt4SP4Gvd6b40XY@rm9efdT-B_A81=v9-aD z>f1`cPWpP6UkKt*TqfiA0{-Z1yoI@)NxeBulVKj&e-^>2bC-v0j%b$1OG`1IZi-pE z-W7ar-!nb;$OMl(jN7Df@9FD(d%EH2=^EDOZJQSV+x1o`Jch^KPr=Dc0j`3qC$;lK zW2l<-wZ%H~|HT??Iscx>Erj&h@wB2_;=bE$N4F3KjS2xs;S;b52&P!t%X**Q3;-hk zKt3}86Su0XV1_r4{4 zZDO7#88*aPba#=n}Nlto5YCu;*|^KwvXazbSVC4y8d{g8@JHtovOTSj%b{4Atb zF^+8K0v|z1UJBIG1!yS*$T@JnsHF$Xnv8Kr>t!^_{HJUSmD$zZ)y~7edbY6jAoHUq%@|} zQ&FI*x1sy%*YA9Nv~M=xog;5Q_vCuM{yeYe8~|VJ6}xH|fxh>i4DG$|Pj4>UbdAvh z=Ycqo(IL85d6;|C_jEw z{7=wflFgYV2J>=(B%T*kG**x8od^PF3@ikY1F;~gBg);?!wnE=czAfg<+4FGLEmQ| zwoR}<4Rf)aJ=-iM-$99epO$V`x_u;Wm7g`{rC`W&{@J4Ffum)_E3cO-t=E_`ajR0F8TAF&WGj|9 z_oh(q94&{n>P3cxWPebS;0fJlfQQjCtSJG&%>T7Zo5>rsg$rhs;Pm8( zJM17(g5@ zr;2DE630)yecWxEdvgA})X$x{z9s%Kg_D(?6|KB}M6Pph?cVWELM4v`$YY}(&PDV+ z^Cpn`hF_##cgYI~K%pH<^_A6Y#hb8ZRpc@`oubX`fjY*FU*8@sJnDJhK3&?|WwTOz zYT*Mji5diHDIW^iX6*m8yicWnmeBM5U>^2CA9i=Ql6sgf;?mlzJb>x8zgyOQ&-6au zozHHUmWp!E_sy@T;1wD4t-nJRa+&e+eRpg7{GXRYlr}2{_!^yY=zUuK&EAuVUL}UcN(T>k(afw6#(8@9Nc6 zo|JkI>t(L{+6`>5H(rHMUXv}6+pfBczAMZlV&}7Zi(wc3ceFW08U6{Z@1^A%O1<;) z{4TtQ{5}4RdCLuqbo2MNJi|X{L1bm!_Rb7nQft=$1AE59)P$_~EDOwb1_2l}G4rbg z3w*D}p5@Q9JjcpCD`){)uO1l=>Y(zjRe{a(bJ+w7KQay<->m=yYv(>bp}zQ9D4q#K zKhHJ8FjW9bb&nnTSG^|J%X3!wL+M$_p?E<#Zo$E7a-{|r)@1cY#%M(?i7W!8hp71BiHy!)2qRKclli#cW34;J< zIzGFr6{~S36{@2-*r}Z7Rf*?>SF

E{~BUZGPqD>iXUz(H)0hv^Kq__G=Gi$rU}w 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�S)aE-7Z#=VYWjh*$? zvOcuvjapl=(I6LuGSRixW?)75RJf+OT~0p4nJbd(&!6?R7H5_zAL{=oRLIALJd$Q2 z{KaaiITy;~qjoX|pHTph&nFNe`V^$jekzcm7JQ2*6dX(Jy8zwiA?-V_ej;P`sik$qZ(xdOMjGFAU<;hQSw(sI6`gd%4r-6W_Jh*>tL z?vbXM4NO{-ec$zVd-~|-@4S3^`JUh_m#aSgjlZdQ=N&v>U+nel0r16Np@Tm@KI+dt z{7Aq3-iufGrT#G`II*3WF@}yo>Pc(!z{o?4jfo^Vx3W=V z>9{gkJ>` zEHi6AOt@}pUIj`~grP79nk7;kGO#ss0o5#D=<&VjIWdaWeA`+IZ3Z4h2}JV<2(n2m znQ8qGZ6$e!&w(chEPR3EN{Ox_HH1Q0@bl*aWT4z6{`=yoA=H-=$^ir$S5P%Xpr$Bc z+y{y8RmSaYGEhmW!Cdi7W)!iUMVRF^a(0C>Y1X({PP%gDa*z| zzLO|dPU=CP6nz0YI7wQCRdUwV*2@{2*E>(v`#9m1i`1Dv!WGuDa-G=+Khpu|FxAH2 zTj#b6)fyf{txcqCKxq@8@_#(}d!i?;P1cwq-mhdarX>mgxf-Hc?~j z{mcqqI8tO8aL9KOjWiWHF*+xJLLLG1zM_z7j$u{S(}N+E_L{5Vcy>#CF30;wZx((( zXko|r>gO5-pOgZ*b-B;@`l{_KttcURFBbb`EMguM^9)UTOX-|H?`u8V<}rl<$FY>M zW*x*^1s?Z2neUImi;t<%TWtS2ACl~+*QyuqECb7hTRC8c9H`{F@$f2P00s-6tgMxA z5=l|hzgq#?#p`O7|5op)GH2!GEQ882|NO!{H`ZAG3Ib%iBrf<0%kRaP4}%4MqaV`H z*w^6=q-(ddZ6eU|)c5_vUwZk?+t0>3?ajA-6YsqDE?(mK`l7FA4}dT6>V3!m<3Icl zF+Tp})wo_i(ms={0r11Yczpl^sK3mM`0wFs{yg}sTMwDj_~Ag}WqAv|ySlQjR(H3f zxv-fJry^V1JQo-V-6etN?iDBQeXoMgx)>|X&m8%GTOU{;OImxsSWpO=MUKzcgCv#m zD8H6Ul`2$+LR)LNTsB-Drp5Ypor&!b+0QdR-w&GJ=kK@MJ~PFG6il5Z_}to}-QI1n zG`i}_3rz$suv5tm2njRXFW3y27$Z(r}D>Z(*>)|vgtSLsH&oE z8#ox-!5a@TnCr!khFMl2!UjCn-@_|}P7vR-t!*iYod{8G(N|1&{N6ic%fyPFDs0kmwJ&D#uLial=3)wE7BB)PDdDi}<;C;$udFh%sYs;Kr~ zMTEL$XuxOlp~4&dxA0slOon92rGNe>3&y%ABFhzYU=X4Jr#TPY>#Rz%j)t~DNcEm) zz1)6;T|?eo&cMU1a`Jm>7e&cA9q}Dnuv;@sm{0YW9A8+;P0fzH6Cw#-ZTsZx^Z$BC z34fEX4q)l5g*?k#*@A`N*!PA=C@IYY>~>G*A>}MdecaaJKpf~L4*cwFci2Pey+e6X zWJZ7Wssuu9+52P#ENDfM5HyAloHT9k_X*e$pYxiyV|Z9U|y z0toW^e1GhNeBp_Z*02IXvCh~-i@eQFhevBRr%l5BejFbOzL{LmRv&v)`8gH5CHw~t zo=~n^@;G!Ib9fmgL-SzSsE{N_zECJ6NrO5|ix6Fe!YU!2(ytQdlw#}CdU88+e-5FS z(!xD1kS(m1{FT>`jEX3O60MwV z;?Bbx zhrJJMo}5nv+NR(c@p)#M=q7ZuW-<5hdu3uAXTa!qZs8*&Y>p{-eUcsN3@PkzK; zZe~EIIAZ1YC^V(?A@3=PK@W;25kb?F$!X+E^2{~f-@slAO3(WZq6z7grH#40F8P;vVxyQe!sn2$?adD7@gP8a;6(-a7?D@o(Z{#La{w@SI;F?G{oq) z`&6lp1Wn>+IpJ|w!Ai=kQ@OKG-@(+ z4HY1Q68KcZ1PPcWvaNeLIe3n-Zbv=2>*QMHQ3B%=Q&OBN*}`DX$$By(sHZ`(@iG*- zip+IdH&NhY?fs*#$b0yjzQ)6k{{VRX8u-??@O*u-*Ruz}7j%gf_Nf5ym9Kn7KltFc z@YP@Xr9L1ZD%uS~IXPLNh{H1DuI?W(8Ix6Gb2S7f1TWOm#yu&lFuFxhC?Y5!_|)yS zwR>-|uIk-;LdLjoHw&bHm3ukyJ*;cz&X3` zz2kPh0t#Fn9v~t*_OAQBhxK@v6-Sja_gf}4Rh^0U06e_7OnJyGJAI_JB(mz0oBM6w z&00R=z(?<%3qab|#M@T0q;J~>58|5%3VSB7a|^C1dz~!sUg2P7@Q?aQ*!MmU;ib@X zmL}9l(zo{>*8kC!XgqA~Piy)xuLV~`-BmrZ?2p-QB=s}p6C%@sj+unc6LWQ|Qs7<& zuIzd!h4)YGK6k$rqq)B~)@^toWHKeV0<4zL!P_K|f|Y7l`n_~s+sq4KWc)wO{c-jY zF^00%TQ1P5BO18F!j-?i<(N`vn)I5vC9G-y%#&d#q)qRQ3KB|$ZD3?sz>9?hOywZw z*?|yu?PSTZB#2_ZsubJH%J{15Lm|sH=3FTLuawX%mfETy9(faYErrlEr^p%W%a~(+ z7f!}o|9m?q=CS8Ivf|CG_|yYxeW5QZ+c#_U#i_=92WU9-SSr5`a1$_v?ay#&KF`w@ zGhrL;Xg!pZ?vg?%{8@_lB3eGL=Im7DkHmH0J{9_*9s^A-xukGTz9Lb1#lQ0No5#<3 zX@CX(GeiqkoGDa`-Rh%hiMGZ{W4nA0&vSgFlIj+70}}GtWsHP&3BSACgNS`_9?kE` zHaLU;@t!QRD)~TIp!VT)-#_}FBX>s^N%T?&fe(u8CTf&@Thdj`n1*JQ3S^N%-JDrL>3aX z6UdoLX762RjJqjOBEZ%x8Qx5S=Z*DSkHHPk#M}pNeawmgK)645i-{MD!yfJq-x_4} zX|>gXK6VHidhepDXlmA6D4L1c3N-*hry_03xkkXa+Z}eea31e0VfMwe! z`AOO=`R3)QrZw2#t0ADZ*2qI)Dv6s2AnwVa=2g(4U1t(J5ex%T=CE6=VMfr0D%xM`@#XZB2RFC2x>zW|s(3ipTi)A2Jy_?uZ<$OD1hEYpHy zr4vWS2sbMgr~_yl2|vxP{L?a5Egi{2YAmjCi!m@73OKN}#G%#N74@=&Vtt$87O?MC zHw!UfkwR1A(c0Vc^g5)(hcg$)*_HYRgE24{0Xe=< z&U)l+@gc|xzg+OM7aHZ{)xzGQNXOO5@FLMxGhu`UDm_YW9oU%Q0IH08{2Ogt$&+ zyxWv?_h|9Zv>=bad-w^w@|+xB^r0wz$#V)XaD~|D9M*BFG@xvu2P@yo$9-6kImZC| zw+pQWN<|6>TC$h^%{d5=@{TutNaNbNu z+xcrla9|YmZMV~xbWip|n5Wea9ZqBaB7cuVzX&&{J&CW+zmp_FeBul*r5#-!ld~r7 zptAKFX4g0}Kf2H4B9EjfiC)7i!xW>o2>O@H1utGaKrpko@3$T9I<1;QUnF1CnXO&j z<=@7rW5)iAmAsva@#gzInV?%}b9IncfC*%McgyxvsJG1lf=mV&nf?WtHNFp1glAO& zk1h8@QFVe4(%7ml=wr-y@jdG|yPMtUJx-&|-NP*opR?wVk>Ch)f&km4p;Kmlz-rNl z1>8ddX8&5a7$)rlWFf1$LT92w0)Ui+^4T8~X5AVtDSSiIYMI_3HNKwLr1tgDZKkMm zaYYmreOBZ=nXzwX#+FqR{4QJUzZ&XS7pY6?t87L?Mv-NC{*GjCp zkdAXMdf%tF3oA?`?-_mOso48oEdH(^&OWE&vI^5H539+*Qm{SIe9LBLWpDlZ{4stH zSw3rbj*7Je=S2)O_#dx``v|}RM35uryBCwWvh2#=S8h4$n{)Cy*C}YC0 zC8f?#hf?(-h2Hx2XOS{bvMg|SW67#vs%iptfz|^hRv=Bleh$Ga&2h+F$t4Nh6;@=nRe1@CWgR!P~Cg#uq zSN3e=o$b<|mM!OegxF(l5B0^S}_TI5r-kp7@%fIC=T^OvK+_zpwY4SKP zCo_Q)rnrnC*Z(TTDy^f!rl{-w&@p6GoYHXA(u zOAZJrDHtpq(^U{z?z$@E$O|W;b5|E_Q(Jjs!sZ5G$Yoa2e;f^Uef9fF#J%NDRZO6Xv zaEll(!H7$D(wWQpOxoAOVszVYktk}04S0O$Brcb{bFYU%K!v-`4_7dR6mKBh(mktv zDA1~@5DU+j0|GM{qcp1|KpqIZ18is*mL~y7;J}mX#c}LHnku>r=Q>iDV$q-pHZN(Y z3uAAfeKcMD6-TAcJzW5a_e7RvSXo6DPJN$h6{aRwWTgc$c?1Yjb2}hGfis}TD}AI!mb52D z(iMzV826&G<(c7sYbu4LM0>}Q-AK3P|`}5S-_3Rx)Lq2A; zcGrQ*;15)Sa3!r`9>b{wBEl_(YQ|Qa877P3x2n$BG3P@?@zd;|G9k*(q-mj(p6pPc zcV5=9=A2(Etu0$gbPo%G(?a1N6A%Pyk9%>V&@vbUCD{(C_s0-p10|0amC7H>o~Yp^ zSo-m{eSZ9l`NV4br+HJppS)}7EI!ae){khN9B9V8SC7v)SYKp(Iun_;IJ-zMD4k8& zT7ABSUnr#`z8VS$>kBYIS8CpBdNvW+_baYXZ$5q6Z?`}G!|%&~b^(6;um2A4!LQ)? z`r@x=0N_i#{w7|%zT&4p`X@T}{b#M=WAcU#frZ4>Lz?NMP;=*6nG~s}^PAOLQy&&X zZk6stVO3-)T^ZC{`X_p1cV|fIQNDW1b;5uF=VNE>U=)dj5$E~aRNy=z+}PdRlfMdi?TPp6ngIls%TUpND#(nR+H{y%p{DO z@%0?b?w$lqS}3%x1pBcKqHzi|HC&|gvOYb zNFbqqrchkA>A}Er#4*EB;lfOe5xZU#5!%PU0Lv*8MY$>%Kq`PG{}$HNb6#Hg?U4tj ze7b5(ixLJLkAXXH%CVgAX8T1>9xqP4@)mwgC&tI~tXSs6Lv+}er46p?ICGBW|Q;JTex~yG#Ub9^tsfarAV%%ve$iv5+ z{6F?g0pMMEXYYd%83+lpE1iv89gBiCm8%g|SFl*hmG%XLbqX!n^K%A9@X74w+HV9< zkW|*^$*P zgq>^6Lmc@mUwj^I`FKD^eC);nP#M)gR3*=<@Fa5?rUI-nddah-P=)ggEc=0jax&$H z=J!(5H2b|;+NN*|-raJaPu4xG<(bdo57FOy3~2pKe}U%cSq@JtP^7MnNmcrE(@&nB#?$w||1G@#?XTkYl0XP*_&E>2=YKr|0AK3$4z~6qy!g%s zcvSqX_uEHf3@#TrLpU}Zo&IEdEDRE_Y9NeAaOc1)~6bFwsOI35SP}Dx0 za5AO06akYnW^1C1cbJ>pqBr;56|A$jA73{+BXOp=fZ=pR96~NI zbaNG-3Gb$>W;g4-3)!Pj;puYOXpXI1-bJwY-7?A#j6NdndUT6fZ@|9qgfh0zFB!p2Fcw1u$iBhD5jmITdAETQIAAD2EQH*t!Y;paMmnkH8ZzBHZF# zF>XgIuPjObS^@wrBDg^63HCg0HtzM$P4R~UIyzjEcfiGI;vNH@b0Kz&WIxnOgn`C9 zJtiEislW;X^jielPzI(loQX@d0EjVWp;Ck}5WsVIuoYMG9UfYM;lIPt5QJBP?*Xbf zFiV{kx#Y80!a_?K}bo;OU^$L;!b(4uDbZ+0305aH~Vb`7gjybWf=x32y{v9{w!A~(YDZP z_Q82WQmHD$Rjy3AuP_Gkv(fU91USr{s5va?v$a<^Y2KEaTcuk ztrqTZ`m~gLPh`&{TRq`V=g5IcW92;x?pKa4rg5%UjS`+8K#WHl>pCh5D^UK@@=(RSjWi zq~bFyW*^XqZ+BXBvqJB^hvi#LiEP^z6#+bH+N_fc^DNj+2$PxUtW9BUT5&U*zgv!n zSzXI=acdAE%lqEZWJGOuKV{5eAl);0xe0&`neo}$?zawzV0+lYdq9BzCZsi}mq-Lc zDc{F6?$7lxs~d#tx&R^?7_FHN;`(@jB8Z!a`zoiP8Q9Idl3Rd!At~k*{KB$#oG%x4%yL_MKQoh8nTjGe9))xpO}%8x#9&vuVEgq`Cc{I z{*1-yX`50TID6yL*R$Tq-J4@yI(M;G&6OjqiAeiYhyLWnw)K5~DNn!jyNc~Y?$1$# zdA>fk>lpy}@-6|OzxR9p;`pTxMD+HaC%POY`I>oF zSBdOgrXoc=wlH%O5*ASpzeSCs4XvNCqt7Mj6n^w={HT5>c_6yvw2FvY13TlJJRIkX zU9d?vRy~nL#ZOk6As0)?z3C#~aJzN% zzNcb1wXIps50=s1Xrg1*vhRJMcSYOMs_(Uubtt+jwpJ@0M9DszxT!>I9M;o|K&GW$ zT>w{fk_5VWBIG*tbxz{t<_hRs6MOH`b|{qb+_dnEmy>r(b*!1$x6Qm8#DN}BTH6iE zZP`5aK9iXRz-G&4C_*{idxroCI)p$Uhav=4ogqwb6BThU2VSKqR58$I-Ud;skoFxb za^8;dha$*MlqW(;W)m%b-&#XvTNgBOAj;Pofe@2q@C0b4{J;Y(ymEq=SA>TSL_$Ha z5|S>g8%Gm`=ik97EY~7y9fVB&&>Ea;rPh1)wH@A7+y?2XtR%!*Q_01L%GuNTLh@`{ zm1HESlS3|QiI(qd!|ACxwh->*^W~f)VZK(mS!k;F-b0@(M@V%IE_W&*VdM>+ap}3@ z{ESq%`ge9_Q*kxPcC^r3>ms*CW=K=KfG{2nU~ozdI(2a8^8^M3C?UX5UL&c)Lq~=z zl`~Pu#h!pu*Sj?DRRUz=%!xBW4oZ-K-^1f%HC*F7O!+3=P7bRr-f3qcea>7u$P;`f z9p;OY=snfx2P0VDDLjL)HB?I)(%Q*s=5>7>$RWp(o~*ljV4DchPU@>blUMj$J#S(B z0g`8F<$u|4@pFRbRj7@4D=!WZzqW7%l0(Gjq=c$s8az}0JL|lH)NJd>*B91UD13HR zuQBEn^?nYEJ2pb2E&Zr7c$6ASBjx$izWUi9+=6pGDI$gmtNdaFLvd$D7x7$R68YIp z_n-XUhkvBs7)`$N&O5-vgW_^IcIEl{+^^>lfG_#_?ce@C;NkN3<@U*kHz@v8WP6H4 zKrR9pmlr6NE-nN;$2DKI8>Pdo=|W-e5MR)77y3i(q-ZL)W-JAE@ zPVV}^zFXaUE8nN)Cf|3<=5DUtqYtQ+`Rif52lm?yy7vGfyo6oveF6w({q1Ib2guc0 ztfBy0wR^_ED`4^b5}EF2zBs{VCEPp<^%%3_KxEyw@Q3-{q~uJ5*r!6On#v|x)RFI& z$$rw?l+rOs7?4o>DMUj=NqWs&s>p@Hu18Bfn?Tp#jn?IVP3=Gk)oA&nVAR7zc^0VPWO{ zy6o~AIZ0Zk@tH-^0 zJ^k=|Nr@?ol+Xv`;*{!C&$uRi;NtZ(GA()(4|$odGsf<~kd%+27kq9U9$yfpDwL}= zp|xp7m-sM}e5vsjR=Dc^BMP~~LVvpi&dEDNKlSw&U^8-Wg&uoUZaKkb42<`>g{<-F zs@eH1+{ly4dVhFt>LSAs=n%3;tfwk3@q0Xh8=n;pIF2YYP#UlO3}BJlrG5`rb~H*r z=BBsXO@HNQKLZ{f1YiB?`GB9V&-Hrt0Qi!xZ+`P%$Lq&`gdhL@pY*SN@aumn z+xAMDybG8j;Ig1=1b-}y0dQsztw zRdt}-_%EAT)=i;uF997L+qPLX0Rt1@W%))P(spR@d|J#1y z3Mg)=@$m@pq(vXh_UO0pR#^BAKQ;THbsTO6*IcL7)68$Xqew3v2LTkY(!nY{c~!Wj zTU5^I z;#ic!oG@`ufn54;eCKZYwvX5Fl`8w7J{zABQ?l?jDP&1y`bZc!6HD-0P|m_=$9Z^j z8n*0STXx2(l!iR$3ci0%o7{OX^E3tr3Q&}1Pm6h3xoXb+!KsV)X^g+prShBOmFH3N zY^QhhBoFwy(tYF6%naE6gnOH zopEvLoT~BzZ`&u8;a8={nRIM&tpZJY%j)4^E8CTj^|xOY3{W{a_0~^_iJ#U!*Sn7w zxMBUeoC&8o{WYL9@^Qp*%+r6E8 zee~IESkLX1;nr^l;6PY;)d2x}U)yHH-M;s*n$9w8HSu8Yv$Wr=uRafy7PWgvj9mng z$j?jljo!g)ylsFd-t)}+tMb;iT$?i);RpqORvvIAR{&{|li*D8XJv(z_0}X5ZU`r+ zla#fZ%Zno|2ILaz*6gt2QMUi-`kspSTt0K0o(rHAm-P_>hg;CovYm@5OYHGlM%1p3 ztvoCrf-Ci|EDQ&utZW~7aX1*_f>QujEd^uPJYb=RuQ5+TImbWeris{CEg7I@@<-1I zKI8a(ep60&?}!NkIA}5F>GE*FnWpf4ye7!6eD^q6{#?y){Ap@qCklJ&KwV_JARV-U1Porvmgrj(C zrHko&6T!LEoM^=PFI8z)P|6l)SZQ z+PvT7Dd0P1VJ1a>O0hwgwW-KSNhJ_8H^hWUw_oIpm}_EQr&sQ8*5gI%+D`}Qj*F`H&FSv)I)y*ny-q>T<6O;dFZ}rc@DgkyI4&pVnY8Jm)o8Cl zAmg@Q|8$I}kAL-pe}v0Ve*#=Cz^vGF7}D7p_k8{NUe5r)7kEka;{-6*{;&VqNBZyo z!5XnrAAm>-~#96 z;Q_!@Y`w0zyN=)X9lcvjr2@C>4gI#4+VqoL8LvNA&Oogc{HIZZ5fG;%eYD|!o; zbQN|*oi6dik{?cMuuu(qL|MePZRrZ=N$PBFW>UgQfA@@V0XOm?z%%ww+UB^BN^j~? z^LJ9-++y8FH=sss=~RH2^cNbC_ZxNUs-f-!;il#9WC;28<$4vs~vPBjV#_=*C!WPuQp75zr{MSH)RE$OI8YH}{oJV3k4#;Gsz5`X6hHiA`&u@G?&@ zS7nPaay_Mcf1nZJ=FcxAkA)adzF#Q3U~v2aAJ(*O7*wzoNGmEq5cWj4^I7m$QuHc# zwE!FRUIrRZm9^}z)Aqod>WBzXD65XR4lQe2bK>s|mgQ?O9qXrp-NHjF0P094JHz|J zbJ4)wt!?v+UX)#3<{&(5%DzB&!>yy@97|qe$pMFTuSrD!7O;*znAQmk68W7~MkZ?3 zPzh&dnJ=h#*wz_XCki1V0)_qP%7^qw(;8w%e1Da1`8<-+D~b4h2CRIX>)LhzU~%mc z0k+HKQ3w98Ht-6Z0NJq5i26jKclLb(A~TDoYLWV$J<-f`s<1p%iU*>FIJOuU%Yq$U}j(My@xO`#;kF_ z_da9uZPHyCbc^te8=n?dH2?tpKmxxZ00G5}!#7H|O~NYQyT@B6a4;49k&3*Sf3?Nh zPdEE!mShqfhsRpG(%)kFHuF>%mgI2e9Z>5h!V@7ZPG&{-n$BVhx{fUK2dgpPT7y_< zgFo+O3f+qA;75WtDP%@TtmdWn><&{#*J<;3@(|8^F2WPTp`GlzdjLdiKe@<2xZdw%_3a6 zkZNyn@L*Pj(89x@GwcDH-^1Hnl-yJ(^5ZdP^2I?OFfIm-K{*G^Tjg@u7VFw^%exmw zs>)SpW&?3VU1;)M7SfItOj)_}k4_ZJW!*6!IdYg^gcH7vGwH$w^)1cm>0jkmrFDuk z?iYln9u&QWn`MY*ebKq6)m!=-S^`4fj4uv~_@Sla*fX4=o?AS`;)O@2#3EOpo^!HL z;N2Hr!8_YasO3XRWt+l{2y`Bn#C0es2ma!W$ePGXD$NBiNdv>mr?Px%?D;|aXQBsD zV$P?J6LY3QOQ1us_y@)sN_FHDm@kX7G5S{e$G@jHgM;b^rO4?JeX{RK>1Paq&l3hP z$~s|LEo7u+j!%{?hXky~3EvJ{9h~f!LBB7dZ%fA*ged@mmS0X*F9HY+K)`Ujd4A9H zZQoy(q<1HZ~MDEHUd zwR*RtKu(adeV<5j{TBEv{=L#|wL08aoTcoMvVcz)Mo?(UD4e|xppka0&P^|ZNc13W zySxJQ5C8P{|MTnb|L8xL@q@qgHs#>?`U0+J0N_i%L_huXm+-&j;+D3Ig0y{@3em!ddlf%cN$d zRcqE0$$gdx?0>OqnxD-v{=>tvFvS(1I10p0n=0%H@mHzu1s^aP|9$7>OF#sBpC$J^ z5t=JC`@Ulz9oO3xvNg!I;d?wEVx)m!L+1zTfYOF%}wvq@p?d~XM;@epgUk-2A~NP zP$dMTqW5)rCfu;!ZUEFkhY2Fg+yw-p&9wdVSEG~b{^<^HGf1xPzGYb9hfb?LM7_ya zcMd}MIu}T^>kC5FsyUFbm!gYjR-Xcpj$t_u=HIOy1&Lx&qt<3v0t-y;JAykLu zQeh?b?8rBXkF8N*H5*lUPCld4Y1yrN4dLnH!hPFj62S>}(HN{P_d;=SYxui-cUoF; zw^omR?wuJzFaeI%8ZMUy0svAGIVb`~J~N`GVdmf!weW@#xjK4rL>8a^hZS{8wh#_ z-(X41@hJTktYe@vjo(sugGOjT*ptAtK^O$$Y7JcnuD4qNFJF1*9bC4{JVWfT zjjdc{OU`4V20;k{V3Iscn7a^AFA2IcoWQ_MRq+*t9~jfW>xIrh3RMIOAYnyyioA!z z@42SwkHb>X<(b%^OIX8E29C2JvE!dv>?2VEFWqeW;1Umfq89IFQ$g)2PFih(N`|LSq<3Y0^U1-Pl$W;lae;>2j2?!dH;MPuy;VC71`u(gaV zjY<~hB~fPy4-&CBdUv572W!1m>V>o|GmM%r90GC1Q_Up?W$?2nP_sw(?zsr5BDy)@*M-_{TH@E#j{;Y&w(QkS2(MoT-O z=2#(+H3=T^_NqI)^}5fh4ReR& zX!LhE_m=mclg7cfX;FRm;RX8MC*MW5?;~BWohc2<0g(Sx)6L%$Qx(gp=pjCAneRHx zdVZpSia8QE@VtO&gOvVISmT@_BLi8oS!pJn-uROJy&z;MCZ$(MyocuFbbS^r{0{J- z%O7g{Z{WOLJ=qhanA^HsKwDkrCFM7Xm1zICoZ-4~7A399k*h3)SR;oeUJeujeB&@S zsP>P;X&W&={2BAd^}DKzpRPMu)=#^ad*7yF&b&Q|<&;Q;3-S{!Kv-9#e&Cl28dOZUGU+{INsDJXwC-{&4 zqyM}7=^y^^395f2ZF^&GuYSDQfvn;v_n{djYmvTP3BDu0l;0<>NalFFRqnJV3tRB+ zDfncswzHEM*QHDB%BWf`V=Qc)4qNlMTp1<=XQ#@O+d z_uhpxnF{{xhQ3dwXpD}hrzhO{{M;4i8743=v_l%qoqir#`)#*)d2`3JEca}g@KSoi z6RkxVKb;x(EfA8+vnkS*a+Xrk@KkWc-`y(5P^DHOu()Ht56h8n}YLr6)!>tc7wbRC8J)_FXORf9Qw=eY{pYU%@PK zXYKJWP~>+u>F$x>mcIF1O6P7JNu@#xfRO@VDy7ApH!+ZWPBPQJvSwxLPnz}9!xTKB zBFQRKz$1lQ)ZFQ3r3T|tW#6<=Hu5(O<+$&_AM^>ai-i6Ko`y{j;c(g zL3KUTSWvHjz*hr&JlS=NXLXXAaz%@rt;)4u>9Nq_ykcO8H4C!1+uRg|guF3oOGGGW zOof#7WnCWwFJC<1-52j*>^Za4Iv8|E03KYTaE2U6gdO-Zo+BYqCB#hl z`H*=BxUVSBq;8$(CewxQ@tgw1TZljqrYXXEiq?WPnmHK=+(01^!qX$hAf;bn6AvZc z#j6zr#Od$e?2*6BAD+KO#l6}T(t)pBUSOkD3?!sccR>=`zloqqQ3xX~E~gf@;x2g3 zOBhhEy)JCNDBn)7T+Dg?+RkOqQsRlFau93At2|6c4bRKbrI-3g-6JT`#))!f#A-Md z_>%$$Zl6cef?t@-p%OBK=aNW_j=%@;TudT>%6_~4REGXU_5Z5>M*z2Sf|b!~q2crO zxn0iyz+Zst{deEhzx3DsIsEPa`oDeEz5l-M`)iT5MRwCXKa^c{*K6Sy_8IT7yl)B^ zi?n!ve$Rw8z0>}^JC2vjcEM!$-JiMV>jC1w|Dw#IJnaXb91orY74F1=#*q+Yc0sRd z1)fCgg!UecYMhAeDbBTEW$ctdW!6b`Wb-mvqd`+w7EXN2y zSqaUG0BZMlSf6Mhi z^W_4#g8^A4b3PeDzpbG@+;TbB<@I`-`=)pFz2kbl;@y`o@yczX-oBOf#vxhR^uBs%*UDKl3FcZJ9fW= zD!kc!L6QR3$BuU%Uf}Cr`5JV0V00>olb3f43-=mhkixIwzRwD`n!-GD<|&3k#T~)s zKW9jxz`I31^;8B<7<7z%x%^AKKZ+>1wCn>#zBBZX31Bo4nRwG>C?fToX$KzDwv^& z1>#1iCcxI_IqX%0+8FrSJMYZ$vB)5oL(*ze{F7>Rpj;ww?YwZC*2hqS$&0L(_jO=; zhPCNQe{A+>tuBO@_5RQ1Z~B(MPvu!Mg2g%d3-Qsjw`G&c{3dR%fU7M3JyIcccS1bo zso$WNhf5z_|FFM)`uKnIcUAuKCm-vJU$E!T*XMUV0|0*ku6N#h58r$9gpXCV>;6Z# z>(hqa^2VD8Ac9^CTREf6l12glG+V>sb`1;rIwXeR9+x3R);}fp8>jVHi zMt@H?`O&*2&W?ow`c3efgSQ*nenC)fJ^WJ^{N1w!=m?&+w}>z{f0g|Zu^ew z^@{hu`aWL1cn8qg2aa!C`I6H$l-mgHSlI{*mc%n9J(k$sg{6hC$QFJzmw(9`4%TATooifW1KhScPRJ83e=?BFLE|fvn5!G|uRy3ARQz3NM< z`67SMWD)fZN0(mymi4uT`Uem|PX1!0r|t;yBskF@6*f;I=)L3Jm+#`2zx|!*oz)fE zHNoJ$&+x1P!1<9Y)SPUU00HkqD8=);dE1!ns{^yF5Ho)oGf^v4r*KIn;OhDyWu08z zL-DYG0rVAR8^rIE_pln{9*4t{Axs810(cMu1!5n#7u?bZT_(GRVO9?c25NYbtnU4R zd6efDfC0x}U4AR>{^dR`bdLYgIKo@Uwn8PT-*;ci@pHh>!aNt}%}qUF?ewVI`@r4@ zE@=4H`(KMbq`H{2%(te7AH0|LLj?y;(no6=Ckj^R;2J6aBu*x6!V656?@$nn!hR2* z5-?9CRULfid|WGkd#f>P5u6Kupf#4XkplWunmO}J0DPVA;dCoQt==cSiv3?j znqD4WzLD1c@h2bs^wnSeH-08B_Ww*zPLb#93%;HKfWIKu>kmHAPXT$_pFRZeN85Ip zVYSZlNZ2XKehvyPF)u7O2wAwt*}mhr;QLZgJYOwk^XH1)9)JI{U2ILd-wN}Qw;f$x8)uCe#7ssc!%|2m{*6j?*gLQ`VOIPQ5fg@w#djoR(kPH z9UAV}A-E1}U+0sYwmvUN!XHe zib`Rf?gGkjlCJPp!ukZ?x317z{AHxjrXo}n8$G(RE-wi9tAbDY{V5M)1?_N7RHeF} zBxlc^0HlY%SB2-Z&Imnk3JVKAeW#u_!obXDUb4*9^3?iQp@h<1az1pN%z*ex`y_knTixLrH;o9AKlSR=*s?DDo07s>YUG2nU|?R z>wsH3^$e{ft(@}hyyj>sI?Qym@HUB+A?9Ne)_Uh*0W5bjNpk7%TCM9Lcx%j;NR##m z!5{s>|LgBR{#*ax*X5u6$N$Ckf#>Vb@49^19eBRx^$&miWBmJ1kpJH6Hy`7x-~7Gp za@j>x+$9xuuogp!?aabdNGLZm80af_SpV^l*=Shu;-M@r=1l^OGYBm_N)@+q6I<)ENe*1Vdml%8S5E5=}>KB(gR zAAAq*Z!d9syy3;e25kdlAJ}g*@#W=VtM31nR1-kR29cC4Ymp*!RZ=XSVx{87qFwr$ ziE$TglK9|2@Sq5t_xCtW)#LKjrk&R%4qhtm30BsYw*|k)&jWsj^f(0u=K*nLU-&&d zcWDWY%K>DXIt|S_zbgiT!*XR$p6oZ4cjXev+3@*DWhH@=O&?@=uvdA(%PTx5=oQ2N;t&*MAe zmI287SwXwb_H1iaK|U}hk{kW(Uln=2ceU8F13})+JVqnwt^@afwrc^I#PVDes)k^b z@8>m3>uH^T24P};6u|(>S|>T!c#?BjZPSBMrHSnO?X~FmlmF`f@PBm!tg0}z@*MB~ zT;-g5Ufm5_(w&9{hPvm z4$NqEbzRX>UHIakbtA7h<|=SX`K}0g1&uAEOobKoo+GQx!$o!J1xW?Wph%9)GoDQ88HglyZf)^Q>BE#jSImZ)?WIXr>$|yBdJ;Cy>hFhL@57P>n)RHL6}Byp zVcki9@k5o$K+SmW9{q0C?-c0yuuJ8TUETqzKMw*zkmC>eY^3Wsf|@RPd$xHOsAqo! zEfjcn&qqsTzj(`-rFV=xwsT8ManW@+V$OrR=_3V7`+GC3+WMw|dU&QlGqFx8xifY@ za|N8ZY|G>jEx-mX3zxwO&n;mK3)NFW;0C0~!_aEtK(XXe4mxa%=QRNuS)X07^F3$b z4YB@odc1u4>J=Uh0Jz?6c>Ven-hJmKzWL3sTL?|EfCvE)0xk?}JFvAyZuf>(HGZ`| zor#hzaJeja?|HsH!?Au95zE{%Z_`y_iXPQUd!-***XG%~51*zCe!N?$XLp8m1Xxbq zn}|gra?t7+BbL!KNQJ(<=={X6Yh>$6+mRH=qsnliyq(TKesqk_oj>~6@q_RE2EO;+ zFQGr)AiF|u1O3|3uO0hS4**uf!jJfU!1HN1?Ig#68XDlBfah-w-^2l6a1t=}Ds;;~ zO@CrztwU{k)NCumr}UIFcm!xtMY9c6$Z!VlE1kg(3KyU?Na}T6b2fjG4n+e}&UG%L6}w8B z3j~|B+V_s@!U zaputHD?ec+pX9h=v|0A&nqQ?f7;Q0sqjQUePHIKv_?`n#3a}jO3jb*x{E8#}El zZ(ZNO=Z4P@>mTK|i6=>Gz4s6Iu0MM9cmJ2C#CluO^5^UGyPg4nFZYU7|J9E_#y7R& zlOKNawBK(3WWQY>1=3VK$EiZ0QZnaCZrb89=S|bWm#nWM0Ll2g&4oFw8agu;~@Yu+0zaT#pR)ea(a2#&^-Zo>7-K{x;#dLfHVQYG}WJ`VjS+pwepW zu?6QDw7@QtT3u(o-L|1Mn=9W&CU|;v0bf@&sb-3gLvvltb)6LfA? zRge_y;L1tnQrLUP#Lh>6;ktiwZUvuw4e$~@00eiCiqK_Pa4+vJom)ic3c_*= zddBP3`%JO)#{X9pJOue;*Oo@vljQuyI&>!_Q?GQdQiP91IkwFW5R76O{?{8xsa)ve ziYgnzb@{grD!@tVmWW`#Uh%8n`VM~cJHLtj@dn)i-4)}exLpT@0nGg|{}~T2?&eu7 zK!<_096CU}AQA|cF)1SVSAYY_V?9rb=cY+z9q{n5_!}8+#yJLjwo05c9|{35z^V-} zKtP}K*{`HL7qkMiS^2wrm25Q(HJ(0-53AL1$Akj2sfB;?;bCVShi^eak<13RP zMfN@dymEM+IdQ7;nzZuoY(8BCC(fv4=y9m}XUv;9>uahF2{P4xPF@w!?Q&5Ce)#(O z^wa;~<^QPt@P|KK#^&4L83g#EuV(<@FUs{BzoGa$e@Fjo|AYT7o^FqS2*{7xWdky5 z4uu%%u#D$@@9^Yjx7!V)@98q$JG$NLbm|l@ZgkoOwZ$?J z)-L3sc;QYE510u(Uad|IuuvVaJUe+&`^{ig#PBi1JGyNEi7b!t|T0B#7?5E8ON zlP1p&^+SGIuKfj(NH7FGrl)%=%(8?c)hfSX3Tx5d=3yDBluU5Q;{B}(LI@t>*a^jO ziYo;g2^5|F)7xc&Hr+xUHY-o)GO3ypF%$90jB0T&2HSVH+s&;1Z*bjjxL&V#{rWX- z*Bie7{a?X5FD~ePKPZhN9tz;w)B8=~6|L_16f9486H8bN=;t3Z7p^&5YI{Yli}_m_ z8vT7@p)WcFj@OwG7782m$hY*Q@%a55?9K04@yYR2t7R&D=K#TB{6bpT9a5}^!lG!L zcGuPZ{F(E$t_Q{tw2ZAzk6R^mec1xQAl>i%1`+Ii$NMke#qa**e-i^}8SXiy(FW9nStcc;D}$76wKL4xchDLJWW;@Ir2_wZ)nj3K93{A;~7^3)n6XLsfqC@rOTs{qSOjnqRMma!;4a;MU?k&~69&WYD;zIj;)6t}%Yk(~n5qJC5TMuh zyFx-QVP}9NqpVV7yk7ZRE&Pw|WJe*n9%V&9mwJrVJC_4Ck-w^(nwP7(mMWKnh(mlRqn} zX7SNf;QL4m|8)7D3!}A}yx^xF>cY5>=c+nXD1k2j1xOS^_H z8x;W%9zPK>MU^~Qc^?M6!4!73?5iuthYADaT|j3!dl|7sz1eZ&08#?WslmiK5LS9? zKbylpy}60c)?=igJ{|AwExdi66j{9Y4Lv3~%>g{Z87y{ee@ z*}A0)diZvrO%BhkM-4H>1vzGDFq96>!*W%htDvQ&mFjXObV${AoO>~tfMCzwQR(kZ z3XSKErR(f(&>0jz>Kp53Tc~A%-u3Yf{?c#!8ovK4-^J_KZ*aX`@%Z#K1vrcQzum6b zZ&%!IH#|LEXW2bT#JV*^2*8|rndhnhPp2SryX|3R?Y&#g-a!^~n4&hv+xI@J26(jq z)tP(1?=y>~TA4z(uArH6s}}daHFKG6@h`Hli)X3VDC5@MbHN2van;=kC;=cqF%uIi zS>D_ezRhSwGc zNNOH`?uw$u@oe=A4(^koblV1EA)8|)RkLGL**Dr7ZwZ-Cb-qgn+GcZ9R&ZJ_Xy?YN zZrnc?Ox==C5)g^dMuRHkd0amnJUDDw0|0D&?E4L`U%$rdH*fIx^n}O9Cj$gt;k)1Y z0Kf8`U&5Q$Z$gP;4K9)tA_deYITv%EXPm6muCF~Sv|o7}ASlxGDK|fUz!amX5pNNp zn8-;3dfFuI73$6zx1@D_P+Shc=P0~+{hdc#dGreDE?vOsRhE$v{^ZSP4+@b-Ycu3^ z6YoEvjhvkitBW=R7`~@hKpjV~fb?u)`pkdV>l1$adw&VP`%nFI*gt*(w4vWRdLOvm zI-cHKvEMqCy<-KK&Vx|cVO|9DooR^7@wVgAx6VNDn(gs$x9Hi+E2M7`eqUku-EA8 zzV~#|wC;K-JUJPQ<&K-yUjr#(qFTC>g-1ZwT5%w(I;r5hJ9;iE7gRNXgkqIRGiaB3xP=HVVLh8=KWxC|K_2=t2sRsaxKJn4I%J` z+#xhqn&tn5gg*ztV(!e7pkZ41FP1N2u4USr4q9QB0(^XY#H&}Y@c8(I$EPQ}e)9&e zUcbT^JAV5IzlF;t*!QdP#S{`ftW^&5^;mZLo$h1a(uAy7At7WVG@=OyaUc?r4 z)U6p?ZJ7c4tO|{~Fuhgz<0pbP2Pv5Loxky@GZf{9JDlNMDR?XOr@SZrdjbOFIbw+} z=b~0_WL0MLq!E9IuJ5Nlv*Mvb^&qNJ9Ev0m9wDWjc&911L|Y%F&zrkGS0iGSov=A> zxL&V#|J`@-Z~cvb6WeP*2GDm83mdpzJ8n-q#y&_fC;n-!`buu?bxml_k@3X%oDJn3 zX268DhZ;aG{UhFws3UJYl0k;BX6Iz5BS7|nJ=HuMY$nFz<5?3tAc0{mPw2|@2rq^~ zd%zTZ0+uUyz>}X~LMfHTITHQO z^JV^AKsr=c_5=#A;xG9tZ&TEBVS{H(oOr7qd&RyYj2dEJ=!qTpyt}Q19N%ANY z#KtA&?UoFf`#_UWdmt%$=Xy7}HTD9asycKC+zY_kn%)fz=Q!A+JC(I`Rfh`D)hh^2 zg|1;*lYmf)H5By`)-D|cJg5UA=`QHsHEAJ)h7nz1NvlX3awRL=`>PT^NWcNCugj(x zN(vGYA%5dL3jQvi)(CSTU}L07zPed!D(IR@Xk8}Ci+H*!!-eOwNMsUf>Ai>Tq3(SW zgbxZhI3ifQ6=F{<9s$;g;U&I^mSHtj$|SBPk?8bj2q82SalipgU#Bw&brJpvFzNe_ z*RQAL|8~9M&6_7&uTS{oQR^N9gg88cKbA~AsZ{{11$~tK2eP86aL2U z{B!uN4}J~Tk6vSIEfT=?K5%{7al7ub>}n+V)0}_6fB?k444pNs;I4qr5~zM=D7WGI zfhYVkfFZ&JqN@^iPipU2xTZ7lrfKJf6iD}jj9>qPD^6L)CtBr^e{$7-un2!KT8 z^}L+3WV7us&9nkmbwMvLUO=|XAKad2BDDPIpw6DIudwRfYAFhk|8#ijRb zIwf8$dA7$l4Zt>OLoS3m6w(AwZ$8C0zwuT4>;Kx{#@L_C3*Z%Q`!$rwK05Zk<95Aa z?;W?k<9fRqIMAa+UL>bm;`&uH@t;_h{?UzS%?0ZU&UmS{TkqIMH+ON1FSXcg&vP(b z$g`4cjoi99zST>%sg*EvWnUErS`5o+peIyDnOnBn0umYKl_p@u`5r`Wvq($)tcS10 z8Vt!Qg!EMdOU0lFp(L=U0O2cw!c>GZ+=J-9C~64^NPM1aPK)(vsG_@)?S-2ibigdH z7_AnHxBjcVI(aDxAptY1dajZ-bRK}3>~vyl;_kz4N5gQ*O+SLuLz+ngl;vL z=N)ezuXuX8;(FWh`1FLQ$19#5pKyD6!oTple--b)_mx?Tddpl7PPbFcR3zj3o5cJG z=SaB23w-XlE;X~)k`9u9wcu0hdbe}HLv6x6oq4&kTmGUb<(A>U=!eo;F5jMBB_sRE z7RWWt=fv9;W}0;;F6$cnjHs25c0-kFa@l~ieD9x+;BXG!$IHd0x@c?(l<|}FPFENd zw0|&brI)gqMAOnIL0ZsP8X4gE`1Bgz{pPpu-~5;V%h*4D#3mcGkC_Xgcl7Iy+f$#O z0bL=k?7N$LPlbJYHaQQW`DA5300GT5TxpJ^ez5mAFN#cI1=La<`Fp%XYRCbtv*N{K z7U;}9V3Z*MBY}iv?Z;RMh*%Q)xl54p!j9*7*-oS+=))xg!F^IfKY1A~G7E#mN4*=# zJyv;0z@Y{#wUAWk+o#ND-Eqzn=a{SPg9O(l@7KNK_H@PV^%di3$Nu^WU+EkEmB0Eg z;Qg2H;LX#cg7j{f3V|`Wb%x``^Vs|5yKMJid92wpq!{MpPiS#{#J*dM;XbtHwg}A9wyDmm8!YDv>_b zaA0Ms3joiP6McLOc~)ZK{vCBM70_^bEcrQ~N$2Nqs#}eKu|zWJBR&^vACq_Nea9F# z{8#=}Xnj8uvvMt|_xnE*j5wzQ{;@^35A zV&~_Qsvbceo)B&N>ZP`MUvX6v2Aydd=bvj$+jYbHVpm`E3^IHFE5NWeBbrqa%58qm zz>`bbzrpVe^kg}&=p%}yR~^_Nuh?H*p^rC=>lORQukcrX07RCEaK3Bxr5Aa1o4g zXzMsW|KkMlx?D@s=n3;{{TC#`1Tb&54Ox8x|iZ?IL5}$9{eK zf4}~dk3Q-@`3e5<#~=pNen9#$HPbCq$0Jo>me@4%9O~`Oo~l$e^yW6MV($5rdnjX zvQR~3TINd+@My+sIczNp4|68AGR?F2tn635s`j&HOwgclP*Bo}92w999MCvi+qPls zJKlNN@ZbJ#{WtLbyD#wO)hq0`8(zKsG%WhJzT>uE@$~eBr>95k`wjbT$KH25U7!BH z?EQJrZP|4f2>#YS_kQ2^Ue1|H&6Z?ImShWCHj)S0lWbhJ0Au3;JQRbefU0uSR5#sK zZ8`{=A?S+efU53lf~uhCE*ylrX{u=qt}-@Q!V|^=7P2g-oU){pl(OcW-|!9hp1t~y zz1Ld5wa>XvX*$3!)8Da`?|%25v(FyZUc+w<8@t`Ec}@2-A+`W44-;m@@7+^e(T#Ti z&=zGdD**-Q`ITL{o9F5t9tB#6sR062-9xNeG0*+j!me#7(L_w5x&h7J9_B6tVwvAGGvhu zg~BD<*~&;BLnH0g@H_(H@<{`cV{d)5d#n{R=8Fi5(7x{x0e&)qS|8RcfC%C=3EL~N zdr8snblL975h|RTl6XNkNZN6gd+g44I6u3<>3NHmIX|p` zA9&fSYPI~*4VpxtqhJ{n*P(sQJDK{cP8uc;4)r5m=fIQ^Nn_xmVxCLxBpW5(m$V^h zUO+KvixB={KP5U0is9N06!a7Ot;Tu}_QdHx(&3J3Ym#Ui=b# z_t*YGY@gVn?9kG&F7~Jw74!KX^ZAVVY{vegqV6l|URzo~4W8+aPV=fG3IRrFKB8Vg zZNQvY`?qL-E~5-2fB^>3WLR?)0Mm$j_l@p2ZNWi{N@-fX3c{3XvKcF!~Ws|^?V1t*kga=4EM@0e*c$$4Xz$-aejW@<>c)8 zrAK`hqHW{7#rWw8hj89Ks$Yk>N`SPS03pLunFCZ+-{aCl;7s zh3-p40RWAos1+XzCbMolC&5a;t*EN&&r|!05c>mdEb0R!ty0#XW8u$j^nT0Yr2<{+ zL~@bI!QYqgBTG#S^-T>P_#9u=;Z;iA$Y#>INOdDs6`RclH*Q?R8(#k!e9IsF{W!gO z4d-WPxOwdcu3x`~eXsJr+s)YTcKxWccHa5k$WcmxJ}C@tTewXD%_8!N~! zD4NE|jkroc-m%aT3KfBj?P6Z;VulPuj_$>kCxnheEDU8bbfU0bhSlHS3W2 zaDb8$Lu}N`?viv{N;P1QYBUf|d6re9t``RXaFE2}zGQ$BD^ZGw5Cx1~jb?~*4LtrJ&dbMn zl3njQr{*@ipKaU+3<{)d2#emavd$#-v}}%0HrtQy&d)x4ZLam`^{>~@f9-2iQ$yEv zc{1AN_3!e!1ORTk>j407mZzV3d{?0#D4V0FOPK`iI6%i2L!ica?4!bF*I`+(_N|cc zPB`SyT6hiE6Ou!TiLofFghGtM7>D~oNXB(^kp!`G3_1*bM1nD(sodJ3IWSM>V13cw z%{a(gCjTD1i&f5G`bvFc1D3sgW^5?hS?s4bpTY0_`me!%@O!@&PdxFNR?zI)HC(%P z&At2Q=Pl&<{G$Kw^bGsm9v2twqyViIyL}6jHZQt`a<F-Cd5xVXS$pZ*M9`?;^c zTfge};QZng5J8ctneYYPf6t|7Fa+)7fffnT>oOz|0G=Y@Yh!!ebN;iEo1DYWU$9h; zlleRx05uo}i53R7mI|2pxZ()I0rPavTFYrl%piBkly9?ZV z^$z@}-~A`?`a55W-KVcL5a6tN|MzD*?9X?Y&u7%#4DBbrnD;*N)T@t`4M|BxFd~gi zg(3oKt3R+fRY3d*zRIECzU|F8X^UMK=>=w-hS6pJR-^SAO%g^B#RqNfT!z_AlG+== ztnkuj8od=6JD=<1AOv;_f!d570N6k$zb&CLR&D~AST+7ZvcosdE%d%n?|eNO(8;AP zGZiib&_VHo4Kyb_zhA1UK>p z@KhNz70eQVtVnPZGbY37Uf6wM;g8!a?zHp({szP-b@7Z)n_2blPrJ@w&Bl`>5Ri)k zkK#a9c*Cgck9;pP+h{)9+@?#r25OIpth26ue~@SFJD+U>!Bs_TedOZ$#c%%R6Fa=? z;fFDF@>Vd_<@N93xjze^u`{0aQ*Klz{byNmrtP|9PnX=SrS%`^-~9g>cLfCEb5 z;$p>Dp$E1jGb~*&8hRFkqC$6!Vs5o-8QNW+9PeV3W%D1rkr8Dban9_Y8kys=RKc>n ztc4AI9E`eb@-1ea*&v1Tmj*E*6?00)Vs%d|*$J`6QLBlX`<*Lr@_aa_N z+KvHq#jdY)glkvK`&>||_5yD6a`Qw6~o3A~J0j5vqdKI%W#Yv(GDOBW9k=kyw(E4RYtx^HLA`?o9NS3lt z4A|HVC8D^o*{o0X3B+RmN+~EE)C)PSrYgZh3qg?c`gn6aHU?rnk;w38-jr%H^OkBPJz6sAmisJFx{nPzB!rHsZi zug!`=7sWvB+ciE)#=we4EzN7_yyHB{ zM7(`*!^od|9NC)q;`_&`Ni2o;HXCMR2Xy7rfq5Wsc;RJCNKFQN!ng^(FqI8%oZi42 zU-@eMx$pgcJa~SL{ZltEX+b^TqweTicY;LJ7OKS2hP|+DHN&-IM)_A~0f9WOc83Mqp5bdMJw-;3}1555^My5}C8ou4(p)dALCVApEQ z(Er5_EZ)<24$gD=Ona|G<2nNEWCwO_=O*U5khJ}Ue#0_SYSi9V<}|J1nnAwS0*3Eb zfSt9zrWb0u_psi70MHFoBV-xC>ByEN2@(+^CxiWHru4WL!`XQv;;Mjrp&uYQ%3 zXEN(KlI24@u&Zff4zS6kzapwzq|NY5&3)(nVanB zp{81aIzvlOie@>I5I{m7l^IhHV@A9>D*Xcsjf_kOfhmZHgO>*{T7HVOQ4)4(*qWJU zI@7fc*c-4AV-M|eL7QDgg5Zs*QuAQXd;KUI9kt`OUYF#$(nMPmGg<;rfk@;Dx*JQI zaZuRsl5X_c5UJQ&*8pfP#(L7NPN8v znP~l4D=y>|=(m%`r9_7*mp{UP6*Y@^*So5NA6{$^R;6ra&sfa>SiOfnzy_kUd9h-Tt%P?$P;H9mJbQu4i?a)yU7TaLyTE5Y^BC^A`%Zk@H~&7o;Qo7X{l;}{x7+saB4`0s z0a|caeu@HcC6g6T{+E?ibdMCA{=~doJQ_y>S0X)(MmX1-!Uo*9>d&Zz(KsrsNnPa^ zlTI)F9j4S3iGXxjBdTOb&}SKMB~U{KFhlbB+yNS;TXN{OO-F5jTbfb9z>9;hd(x$ zF)H?)eOX;hx-s4R;`&=}$Rh*1`36w9IMGJ<9u;HM<$FaVflk{ak#{ouV1)_4A$ho={O-Nobk zi`^r~CnvLrsG>H`IYMoK0sd;Va$fQ=xkB(*px=|hB@s$;yexpc%ielc+ zD5ZOsDYP~ptJiYpr_v4Ie$Uk3Q>$V#O@m?-9x^-5FT&-~6;J~pOtB23oX2O!0vh4)&=VCclUgI|bPYQJ?F?I%R` z^|Rb{GB7J31(@p$Ma49=ynL;S8FS0ir!}go$iN8gY6ryvq^oLxL^~?bqe#${em_~E z8w4%FW#9gtXUrO5#=CjO`9-UFvD@$Q>Cb!`58QJ%zWtluhL^nLLEN};4V%rTDTSr@ z{%FOgdL%&qU*Z{*{*(XDwz-9j#=hpN`8KBu)st=V+yIu90K*Ebby#1h*PaMoPJon^ zZyG{@W%xI(S0X=-16jZH0MFF%55~3z0K(q6qM`@?H@f8UjVLbQ#+0Uc$}rucYw6th zJoW!p*J$Y5*Bwxah$Kk>VLUIW6k-d@jW=4cM;XuQmZE`-bDeQ|{b~HZulWX?U!3Ew z|Iq&pkL|BxbJtbquF01fQwQT3|HP*6iONGUgTH@a9&hWjAG_Dm0NijS-jgR&(>TFA zilQ1A5otA1s~hXVCuy$J`_%bZ4pkxRo#b_jK)~>sm4{@c4pmrUYcf$O5-2Wv(%vt6k6{Ti6}O;ChKT^p}?$w&h12%YZthCzr`Pa z+qdAYZ~iho^~^I(?{K5OK&BRPRZ8(fvo5$@uPc58t)L&(Y@U7g`WNHB;s8ZXkr4+2 zcs=5MHbxs*7~MuTtNfW~{-CjXP{|!uj5TRTa1ON8oJ6??rk4}=MkEs3G1EJsDi-#) zrjal8kF^dN0vQZ83@*P9nJ>HG0ly;rrM&~)h%#Xro{_8`t|@~dVA=R~ zxykcH!Z17--4ij$fIwT#ATvcgdm)yS8{P-*eE=%0n*QnO8K!B2Km6@~0C(MW1wZ^x zeiS#ZU&Ebu-HmxamTHpsKon32$^aWHWBy z$|Iod#BkN_fv9fm`as;n5{i)l5oSEMLW5Dk*uva}3gSr@w6(8gapf*(y@1YfC(3ob zi^MG-r-$(gLiQ~(!8(#XK}rU#0X7}vkp@0k51LV4fC|h|FOaBnqm8-FeLij4H0^(* z06W3JQX(u0#%eP}=x>`5g@x)Oq5#ke{>`&u-d*&gEn6Lnx#t=0=NY@*9{c@_)AKVt z{`li~`Ac4eZ~NwN!h;Xqhif;kHL#*9j;>&K;7MT0X#XDKYwgB}7X^gVtXTa32ME}E zA)$kdQTqT4FrBh-o}4oDjK*mpp_FQ-7mhk1R999Efea)Jmm8D({^6D(qGnx-(|rF* zYFK!hvX<%I&I9RW1|<0|GCjTqA7xZw;ei|afA}TO?pl+qIWE8=x;zP|+jF7;&!}TK zqQxVm#`bv3CW}nLxBV(bu;0x%z4j!&_3OV8+wBJbm;dMA!lREpiKF}OZ2NLwF+<~I z3l!8sN13rKSGcinMfAjeaP}1ZKMa#TIx~WWN8}6H*V~E@hU~Gw!G;DxmI?@DmXJL@ zL0W_NcDfcL4-Ub%C+vFyGL*p>$y+Uloh-ghL>&c#vlEbZ^c)B(VKDY+B03>XneJl zsh_S=8jvq7+N2ntEkjC`(BAJrt*K5J3Y9YGY7TKs2ndbfZN9&9a|J6tKijVxJ;v*jcUinI%>r3PRxB0pR0B)m8L{PQr zotr!5O`FdL-u(GzPkiztzkYJ{>SJXpch7V7(ZGx&sdOf_{|MKNI3Fy(5!tYyyC@8P zeV4LjY@zHLjGRsIVuG%-rh$q35k!WXuK*aW#bp-e0-tr`PO#8iLCVX}|4{hREk`oq z>76SQIE}+YJCpapK!DLu4MC#jdJ#M6&W=Xx#%4F5&(CjScQN6czTs=}-~;#KAN}zE zj7LBINt~SAfurLSTwsUkT(H?}u-$I4-EL4e6Q(jX1HY73+1EacK+G9lO7l01c<8;Q zD7Zq^``%;_<$RK2T@x~lWU!j??3Md1vcTpL6o#iw=4&;hGr|B(snM?Fr6PS~1l#An zw~_+K$2Zxz8b(BBw3rgbibjg?uhqdL3+k6{Bx_g3-GBqSVi#ecs*36YSA#y2QWE=6 zfKC@R#-K`Gc*N0rZjAO6j6$}C>6D@3HKr1i{AhXSN7;38841BGvcEvnno@eZKJM8Cu`*%P0;+8X>dFC3< z&QJ0AuX{DV@$0_^_uO+Qp1IaM|DnjmUdzUGT>Zq#2Q~~!teX4x8i~O?#18y2Y(mudgLM0dylcb^Ge(Q^V}kl>eLFC zLY6YjB7bWXL=C&23`(~{beJjrv?d=l3i;6zwB@R9u(PdP*tiXXcLU!IcTSgLkf6UW z=$!aZf5xK3cr}xe?Q3oZnFImT=?N{ek0JuZOtiuMVbM}bb4(5eU68IOa+N&oJv9Mj zAlkfF|2hB_yQfl7H%)-FPKs$N*odH>&%pHyyy-=+!uNdBx8irb`gM5b+A~0DzqhFh zz}1dfZGRJ2?sCI;W&wGTjTaT?+LR{X4Y*j7Xw*TmwvO<3!rSr1p?~w<%`j=ES4sCL zj*!|~KdXvlI?007SS#i)7AqkGPj44j4omaWeX8O|DHGx1T zK`GP4#rfI$cBj{$!i`e!|NaMxzwy_B#~#b*;^p<*dOd5^;PU$Sb9EtD0LTa5`Tv>k zeDN2*{_eZ){clT=uRA}zG1pp47Y-sQTFQh{CX{L8XReP%wuSAa_W*vBn}31G2Oau@ zV2Y&9h%wkOZn^5YU86F)I_}=lTjc?%R)~i=Xb)v}6)-bU^5AAnl!UovX32_d_+|!W z(kk4qaAo(rs+n;CHYDSQ6bL)T!3}8o?BQ0uu#f=(7Z(?p>x?T`Pw?1dkK-r)#n0jw z|Mk1^%=J^8T)Bed?Qu6|1k+~1cDu#2*@h%+5%i-p+BmG@WuB1q?fJs!7p0n8wK#BWALe7;S;r3l&Bf7A48NvrMO@(e( zR>(Ar55(LQ+kaO0FZKuHWFCSqp#a&>0U)#vRMG_bM1la9Sh*m>+qI^cw^FRaeT~}j{zjh7RZ{Eax_uPdqd-E6JOWyQGY^H+q-LBERd7p+*)X6?0fH13<(f-%KGNJF> zyW|RO>>UAGcqL@xd|a zmvK|O$BJ|s2WtAZx;8F=_F}310F@dty#uk;iuh^udKsz>G@JK(+;!Jo_~g@1dFl&u*dwzh&NuHd$%Sy(lkvLG1-jH4Hewq7u3+aCL$AC%oS_1y$iR=iq`9 zIQ2)&6C8DFA~d1I9Fsf&z0dk!^jBe9og#O-tsgr{p_SIl`f39=#jjS#jEIKl`%yNn zrjXVOxqgBBH&^gAU-D)6*023WJaG5Dc;=aFjhFQl!j^s4c;}`^BU)NOD-2jrrf^K4 z2&Ph;7eab$nnx3uXTNlw-T+Vs6GYnF34mhW{mescUMxz$J>AS-jaRa4>+&)4*TT&& z41ecIgvvNCNNop=@aq=&|Bj6Ib`%SK#zDEHA*=%n=sFCsFzEneNwT8Tph8qtYOPvp zJN2RJ4Cw6oRw;tbG+}#uwVvF0|Km5FdHgTD<467Z$Qy=>~1yo3g40rl3Kcm!#xTU5!Rx;46nrhXyM%9YZp@Rc;_V2XTejMM7i;Hud zouA=o(+u~A-~VC!?9crY-uM0wvuR;Y$XN3FoP_ zqYhcwu|9i#_Mn$rLA@GURCtHQsve`~)E9p;hqczwMM&98x#isx(7q?QhK&D|V`~nN zg-6f&2@<|S(H=-fcp9zKYkof)BxY=z7mw+B%$2vW?_?zQJ!GkR0j%{C2N=NUv}s2& zT4;Wx0|_Zl5l(3Z1)Gjyii#PxeLmCx42`McIW;BI8nUQ~?s4(G=sr$$I4X6qM(bv%caKvT`qFr*g8&i$f`MhOOt!FASF*(-XFBgq2$A@ly3y-=vMZ|+ z^pFj9C9olAOgg|4tzw0D8Uz`Jw%6Ej8gnA{C022o#DzXQA*}wK@_4mh{d<~OJR{@BsC{$XC zPWZRYLUrJ;^GL7BqU{uu+AHzfo>6UkX1BY*Zg+tzSFU2(Zt>2C-i?3w_8-N+diY&< zQqOR7|DBkw9yh>l4x`YHT{UBnbAFZnM_u$uRAHK@aj;Ru4Jb{CRl6tJZ2#y%I$)4Y z*${`MX9BaK=L$oy`%lFbi=<$e74;2(o+VEe;QAnq__@a?}5`-pT`8gvfLh04En4E zK+1dzes40#y*WzvQUe8eb(pWgR1gPYR?RJm7bF0Hz^;)N<1h7n17UhxdlNBY1424z z6pS8P3@Ayo)&Kz1Dz&Q4^9-#G0MI(ST%4vNn`zRclRI^LOfBy?V_Z`4L zD!{%00P&w#P@9!OjZtbg$)PLbw}0i?HUd zcxqQPUFNVSWCh@end)GaNJGw9;jwkLNQkaQwn)W*%ER~~a_ay3@qz?ax!0X&k9Pno zh&~Q`ox2L?Okt-74LVMM1CwDGO!;IAf;`;F4msZ*E2Jd0=k1h*whsf;vW;oSF!Gl3 zeo3pE%u0|OtGqo09h|YfF)AgQ&n#>iniH?yEZv`F(HN%WLt7!gI<^xExD^VlwdMJr zo}S|R_3OAeKf~3lCwSuD2-> zPwKFOru-+qPV>B6aigojD=nguxmybeAW;>-Picu|&XdAul2ZDKE`l;`umN!M^c1*p z6K{UQ@4^?n`nCAEU->1x{pWrLzx<*1;)y4pfL_^Rx^e>9^pv1oOU2RI4BRjx9bhLK z@=)|b`^LzQmr#s1i?`?WY9|fUVU1iE^9d>vQ*1oP!Om4qTi9CgZ#%{;+OB2k$yy&@;V&9An9Fr%wbr z_;0o$2E8?+!nmP9F8j@6-v;2;$4?r4a(WD;+4Kx;iFgDzC>52$|3gA?LJL=<~{O}*R{rYI3gCDW#dwytL$F_U04J#@T4zn3A6Xu=|CL+WxWd6V3Ol)VCp*=PuG z8Q$SvvKE^)+5}1@jA2x$Mvp^pD5IPikN}VzJOO?jQck0JzQ_OeFm$C#?xy@%3IJTm z(jJQEhOJijfa*MZSoD6k!)||p-NiXhPj6!0&)7~|>~?#6;?tkOhaUYXKJt-|;Zu)2 zhHE!YG0)nI18%Y1Zn4>JF!jihGBuOfaYO9!zm zH3qN{ew#;5?J%PRp{yn_%4n~cCx#+KBCkEuA@g7INZFn-TK2Y=&n*WUsCjC; z?_^2uYQf<=6gwZ-)5E~v9>k-bdg#BU02m-R&ogFi^)s~2s8un~GtSS>PzrG6$`PKq z{xsh6!S~`{|Jr--u1DUFM<4$*ZthR969q~O)wW83Rtv`Z&32^Ii%3*yXP)M|fcod) zXNV$${uw@&`{ID-PJGqhHzb>C7_=h{;M+GC{@9BsEUZMOU4yY9h_ zYuA71Q=jXWiZze@C%tn&lNX(+ZF%{xZ8V!T`7IoE+> zkij~s>n{zb@5kVo5b1^yCiF1R8MV&XNk zefQplH@@yQc;cz2@u|li$EQE@I6m{(6L{v@bzEGW;^Mp+8AdNHB4Emfg-|$buNh%V z8P6MoB>ththuca;s)z|b@<$fL9pGh!oGT?gv22_H3gJSG9!I{%E%r>LFVw1-Yf~VachTqzP-({lZl?|IxN?jK@4pWZ zK5#!?_<{#;@4a{9%JCLZ#p#Wkm^NEnxq1c1N84uTZ#%HH*|uJkl`{YUfB;EEK~(Vn zP5+;V^+&#aGZhJmAcC!d~$bf290}D;Ul< z#0Zx5(IW4Y=rYwnp?5p&lJU*FdB7e9Mwls+e`B5yV?*X4rqFv2jYp5r0ng?523@vI zO0>5@?fM>HZprv-07Nv+L-1qX7x^ODM-MB%u*qHZUTKFx$@q*?wapu6#R0WQGtNbz z+T=-*7Md%y8DOak3c1B;R!|MPPwOw~lU0UTB4YfU(BH9FmBwpz zdAV`kfkCefLbU#Qt)8lo3KRvl5S)|=SB|dWzWeUM%U}2+yy~Sd$ID*)BHVx9JviDP zW1cG>fBFerJ-LdjCwDYnJ58828*HWxCfP)^Rj>MQ`TG-{3}C0I6gTQiM74DJWZ#D^ zUclk$*=P<>hUJ~jM$$>rP7W>{#<&aHfrB3Ar^D_CpP+*Hmbm{K&+kVha|EpO^6_;M z+2Tp}n~d#RA9a&!aR&-3z4B>oTYm(XyE!Qf_LU-}C;5Klt>0 z_q_<`H*Np`UU6ys-*(p}0B{>$ANascJ=<528&7}ou6yozs7%uryGqt2TQmFx8r8-! z@@p3(&Va+tCX_i;Gu@vc>>>LgzIH*XaGf7s!PgMp6kV{w3>=X~2;uY|V$~XKZ`snK zJW6~LW4318sh<`kEL@f5kd(ZpED!`Q>qctfJo;!Z{#M0giX8(ft~|Gqa&f2Hk3!q; z_n7y)6z(Y(t>D?T+2G=|)d$$^XUJ4=-+lMszWeUM>tFl1IN$AXTU& z#!cM3c>`w`7ufCgxVYHk?EC`f=jScTV5TUAYI*wYbEWYlqj3;$+AGUfR*p=8vsnt& zcLn@eN4c3|1xcO`#1mn79I~3??@d(_qq9&AD!r#5Xfr5lFzS6TNbccSK(dcNl$YX- zxUM_yOWKE^LPTNHYYY9=0UV1EJ&(v}QxJ7423Vx;k$g6@?N1NYx5=PT>ShC2Era%q zDTW4GO%NPU1>2Jo93LOy%E>XVUb%v+cbwpkt5OY>$qx-EMJobcAVp)bjtQrjV9i>!O95Mn0>6!%aJVh@ccx2+e?K|Fe)* zn?u*+{61602((H(OvdFSI$7Ki3K%Phq2DIx$9ItP+5TROI<*fi4tjD&N9N3X&pI#3$VwZ% zKj=R31LAGj9}U1o;k6)wcPzVvf&Tijtx{D~w2%>6P-;b~1@l}{TMaHubH#q{dDc>J z^YkXJ-MESU{sMO%pWq8#^?CTbSG)>edHr|eu_vCu<4-<;#~yzSk3IPWu3fv1n`h^^ zxY*%hzsIf@=bY72CW`X{&XeYaiD_Lk_LuY2LXFuNj+!z7?2PMZW^c$~dQtR?F*(w6 zwDQvPigPN79S=&NssUxzoO|!1rcJJ`Lvm-J5lwRv7FVvE;Et24xbx~&+*tsd!)Es6vB1JHHKsJ&w#R2yZwQszVfRmCQ4ezMGAI zry3ddkl#x{r2}oukpmDIgObL-Q>v-bcOy}+u$$IiB|0AZ#;w$4J3-4rdnJP;DMu__ zhR!tv(#rk!uV4{{)@oHdyGVO5qPFr(kXoiq*`3{d>f-eLz2E$Y|JcR<@Pj{C{`uSA zju$@oATF=l@45s4ZnMkI$9&|GM^Fmz!Uyk@CmwnD$@A|l{j7i+Y9%lLKbEUR4xmWncnb?GSYbC3XpqdqT@h#v zSdpjwyu-BHW4~{?bW#dr7eZ7}FijgApB!U9&)Dx<1i`#-Ai`A$9=M;w{9B&&tlcxa z-{a!q0_W!!xY%7_-p{=vdw65CwxV;2nH=Dh2Tp6CgdAJZDi2*{6IIuQB^d>#5YbYH zFb|O(bk=E#+Hw=7YmP>Z3+39L>*K1H_e!9E`0e&gHlJ5)hxnUm9;vbPum~FH!h2io zbO8&Uq%g`(FO^P>fKt5y4znzIXX~<8u1K2aCiYL;e+~-SX(Ij}c%RIOoCl39&FC5%Y5$eTNElo;_V45ZzA01)4Ir3tEb^?pZ zgUpoJARm7B@kuNK@nC?f)c1Rd(D<|XbC{*ChauYWE6qwo7Zy$t=oJ+Dgu;5NNJ_j5lN=-ct=E53Z*UtD|d zyg&NPG;LnKzqn{|H?G1|6-P~n2VAVjVF+5I)=@K z@(_|u?>D2$6gSmoobPvg?DxBt-?^V#D0MHEx7lo(GEg_rO~tlqs~b?&!@{kSvmYy{ z6-6s1Gt7@FPEM{+=_r%eLCRP}0A0j!D5wjxTumWbe z{Z1m+f%L4%3;kCYVzuv(8p71Qqy8jWNrah=RG#;OI)SgkR^7lGAu=;cx!)dIYrsze z0Rm)62AqRLk`yoi9g>o;J<(ojk<1-2RM zrtG;|q#DX@>8Dxr-2JAbRP6UN$~1+tUO0cWBqb~FQ3E&;5kdYNgG0Q}i53T-GOeyp z&uGz)=_&LpX`Zq|TkBW;ZH;rTy@!?D1Oo6prE=rrH5$#)8sX2mc9tXxOuY}*yOb!0 zt_&OA>M81@9=dsmFe3nFyj2#@jGYR^fOa;YQJvlb~{U-mrJoc0bMUNnC{IqmW zz5uO0P?`Gw`iVi(a@F_yT?c;J|60}lsXJHHK?%6GpLV?D-5+|8@mm9b0`TDVqy-?+ zvjPqb4uQmi7Vi#yBY-3A#|ox}fSLDWTPwIIzvUq3#_*f%{e=C3BX(CgPV_(N_Zey! zun*Oq=@`63T)z%_sa9`@x1@9cN?RI0*$d=XpZfHtKmEmDbqzOfUf|}heelYas}F6b>E%UAtvw;khp!lt$m>&BW%vE4fPcFVgeePju?i@%8mlaE6bM>)1Ka8D$KjM|O_PFEDt5i^5V(TvGD(~(a5y`Ss6pZD1BF4`HawPL^Dd-eBr zZh3FNdu1yWWu9^xK{w(jpr;j7R}7k`AFV>Wh6iJ=!TePb!oXc1dAnZ zN~@g`VAY*1_mhudG!Vc}6)=xi^D<44iHr~xXsy^^?3xF$&e(3Y%}^-?WorF$AjF1G zQ2+@b5WtE*`F0*?-IZ_}8?P9DV&lWtpA_3AFx2ZOj67#SZAP|@u$Zq@ZJt2qNz}(% z`i54%Hu_xx9qI;DR?g#Lq5JZyGEl&X7<*k=rkJBX4jphtz}oWS;#yaw&+8JnTLTYt zxi|)XGB}sUkoR&xK4bqF>4Ad)Z17mR8f~&n14KN=@8_N}w%hNp+g)HkH{iI|*?EwZ zg0kuI0PSA^TI~QSn5HfE^B(iO!(4kb+034D0Pp*^0;s2)9=%S4k z15!Lz(KR8)5jj}hqq?Mov1-l*+ZJS8mooWQndtPN(v5tJ{3=$suOHDj*)e{W&yU7$ zce`^xwph0nleQ>-vKb&o>I=C`C`D8`@NQ{}QY16e~LPJE~e`-b%U@GZz`T%lL_SHG&R^gYuN; z&J(q_PZgGmV|rvq`T!9XX_B{;sqD^9pE*0d@t#jT^vDf-)t|xDtGj21-d;D&UbEkLyrEidrKQMoCcX9D6)5*yfx=={AB)PKWU|(7X(##CTn+pUx z%FBehd;Zzr6D+`&6@|GeLo@{%9)DBf>b!5B|9Rh)x90J$wH;5@41c(CWrmZMRuodS z8BkT)(N433F<-l~U}5Is)7Xmk%ya9*#u$HB^E|wq=9)c*DSe@TQEk1=17V82QNmb3 zl7y(ze|T9u1*RJ$cJ{mpz2!AzTsysq(_d9uUlPvj^&;Ird4rTDfB^tDU6xgQb>-u_ zW|*d@hw>}JK<0|d5owVPPR{dQv|S|d4ObjZQL6!RIpbh&B^khlQc{Er_?T@PhbemS zW1sS|P)Kb>!M>>2Z>a^Adn&!?k$F8)wbcfYR`9wM1vE}6@T%TEhrDN_x21$aZnNf! zp;6Te*+pKdw?u2qfLUI0g@y3OJ)f1eI$vbIXG~@Qs6e67z{={};x!&79Dg%>wQPlt zo#pWi{5p}PM!QPk{%bb=z;4YTJv^E*I%n)BE6I7)4e3t23s|;CAyKhAdMQ*{;RS}E zu#3-rvVBl;zf+F70k?DY>i_$BkKO*FMS>Y{YjY@;_94)zV8TTEt)O7Wj9oVpTMdr6 z&d?1Tv^MbImD!k&G$96#Ev+g%lby#Z+~^K~bPSc2MB+KBjF#x2k%O==dEH!>py?;j zSOv`k3ZlQhUP6W697J~c6=QTAN@Ewso*nv>3|%vZO_DSL86*=Rgrydue|E6Iitdu0 zXCOe4f~lW&(jow;ntz#k0aYueS{x8ar)OxzZoh*na11~;8}w6+J2NW~Ki0M%QE6!- z5_9k9o{)r<76oI40b}m+ycZe3_x@~2TT0SAGq`ATEA)8{4rE#q`fMP@+9l}vXj#(q zGLFFhT1n4yUKa}~{`b&h8QvhVPqN0n;M_{z>CRxNZlMA$$me7==#&o zJp3i!{4E`t-Cl}0Q4XJoP6RJKXQK0-EVo<$(5_0+Dx05Ud(%C zj;X3zYi&olu_fE`#Dvrv!GB~I^OZ#_J&A34}z@ulqH#9CV z4V6|R$1;KJcEl$D<5H`E0F>#UREfYc20+kt36(%=;xsqgnQuH-sZkzw&wAR5PAGx4cuo%MS)xYQOnBp}5Cydj<>uR42@F^aq-{-|L zgoFyQ`DD`DNS{cRhb*L>wJdlH5k39ex>oNGwcDH(VI`(fa6tUo+tcqcpuzXn$lt&^FNsLMxDt4-F3` zgNp=OB%pwh3s9X47JV+SLS`u$dC{1X3~NOKC3Rd^Q`0yy&TZK~38HMUF?d7Zj~n`g zbW-GtJ>-0}8$oF`Y+h>^+ZNv4ie~P~V`t3=Z!s`UwL(izo3VNsom)*(P<6trGd6V- zMm<7)nK2)cW9qU;r9ewXtzCv_n8I*SAch>ZVhn>TVSo^=nOyLHO=B5>RNt=y9WvNN z#B7m3YVwMy-72+{ZR)_`0uUSajsiGP$G}N}NHa$x4etR`Et}%NQH;TPp^XVvyP!X3 zJXHa&tm-1Htu=eW4AL}+qNi>$=0~EfI6$Di(dTF zW!F+K%#lTnD2mH`7C>zRt*~o2AiCV}=mDj<%DiJ(ZSa?4gj_melsH2e*pW7e-c}Tt z4nF7FWr)?(S|(*3I+zsABuDu4B9rwJz3ZaD6!nQCR){NcX?kALLot`}!vb$Q*E*ChaOJ71l1JoJMhI z1Tz2yR!n^i0keB4Y}vdKc0*Pjo4Os}}V`9(pnK2fey|_STsf#f^WA z9@7qQm@&YB3rE!SjqM&v-%k>SqU>&0Hcf#hkChqo{fy|ag+hTv2NgVA)eLaXe@%cv zJU`bNnEP+jc!TH071jIXZcE^ly)wUl{Y^1ZxpjawUg!C`-q*~UV;_fcm#p*@1(1r`(4A}id%Pm8 zz$Tg3N5+n=tSj>pih6KqY=b)#cNbXENDkgNyPZ$jR94;jJV6cF(K%F|o9wW(EfVNJ z2iH&=KeuY{Rked}G58{pQZVU+T6zd@X>Y2PczzG$i(gi1ts=E!th)kXySb)^D+SCi{B#zC5$Zs z!F`RD_l^=cHxP4Q=Efs?r)1oeqzBP0Vy6^XUwVB8=d-<$;O&iHP8M0!jeAJJ)Qb%l z6gp0|1kme1t-203&n-m}BG^mDz~CeiNGY{!HrVgyi__E7hqqU@PrdZzFS}LGFR$C=xęJ~>BzOH(z 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νrl(n)mkAs^%$~L{xw*Brf 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

5hSFw3Otk^{_qWX=CiKl4}R#A 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 literal 0 HcmV?d00001 diff --git a/mac/Resources/MenuBarIcon.png b/mac/Resources/MenuBarIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..a1992b803b30a1c3e677cf4e4b193f579f7ef82a GIT binary patch literal 453 zcmV;$0XqJPP)>GKsVNPTNG_` z_jM>P;v?ST4L)Nj0@_G8=z%J!fIuqUQ#C`$iCGK7nwjtCys^-1*Cz*o?-in}UEMR}whl$ED zuX@f^bO+C+xmm=w&<)3<_IDZJ2bOUTr|{b3Y)y3382yntU|g22MV2X}QOL!*99)Is zrx7=sQTc1wrlh}Dhb{Yk4_(zrmu?2P@h~c!#^Femr@o88>j7~aS0v&$a?6*biC)7h v-s4sQGe0B8@l<*~R%2c5vD2S1YUa*OZVjzOx8Ec|}CFX8sHeFYCh{yQ*^l_h|w267N~M|c)Tajkm2#uwj zxC^JGRiD9|IH!r;9e4}}a2gu}U;%Z$K9RxvhMVW;zLs^);Yx~R8bGE}G&b=K)-u={ z>rx~?&N@|Kskl%2N;e1TnlB?%6-tYeeV*}sk@l92vcpAu zu!woPWF@_Jgo45&=^|Qj{l{=LUzLD@B{%RgZk6~UPC!KMZ+wcEuw{8PSC(3{O-FDN zS1ftvCfulD*IJ6&zT{RdMPQ0!8O%(g{12rlgj|CcB$7Ldr|_<{jWk=_mOWL2%cMd# z#fb%aH{f>-r+ka&ab2NvAcK26>zwKUHqO`Evd%9!Ov6L#nrOY9=OeLW)c6rU&EtFvcrWR4zL#=RX%-iey($Aef>$*u@xK5nWo&2ncL*=yYRk*gAjLKE zuar&vwG0eHwsSN=T%DrK6-nkL$3aXf>1PD<|ceZb5Ev@EP; zn-bBuBpvtnrCRqZ9xn9zu=TEL-xp9wjqfC8KOwcL@qCTTTh4mfEdfP1myuZH$}7@w zzL@{VtItS^Sa46@1y*6CEz3?`+&`Vc#0y}MV=QddqB@)ByvtDeCK_^3)4VULTZ^`9 z*L>Oamv00k>Eu+c`;$B$dVPAJXiL`YPK)5HZP26QMmTs#TJct%pGY||!97w75=ItD zG=8^89cb8?Xjzu(K8OwI~?v&`g&&Po`@-y>JWJLaW4_g!57xvmYvVmuCy`2dctNs%o;XR8# zpOki=mRjC*J4qFOl0L_Lp@Yd+DYX0j)nx7mtC8g^kE7m4qFbDIA|vs)8@T@fmgMvx Tv}Dcb00000NkvXXu0mjfjzGnL literal 0 HcmV?d00001 diff --git a/mac/Sources/DroidBridge/AppDelegate.swift b/mac/Sources/DroidBridge/AppDelegate.swift index 0be5ba1..323b1a6 100644 --- a/mac/Sources/DroidBridge/AppDelegate.swift +++ b/mac/Sources/DroidBridge/AppDelegate.swift @@ -110,16 +110,22 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate { capture.options.invertScroll = Settings.invertScroll } + private lazy var menuBarIcon: NSImage? = { + let image = Bundle.main.image(forResource: "MenuBarIcon") + image?.isTemplate = true + image?.size = NSSize(width: 18, height: 18) + return image + }() + + /// The bridge glyph; dimmed while no device is connected. private func updateIcon() { - let name: String - if capture.isRemote { - name = "iphone.and.arrow.forward" - } else if case .connected = link.state { - name = "keyboard" + statusItem.button?.image = menuBarIcon ?? NSImage(systemSymbolName: "keyboard", accessibilityDescription: "DroidBridge") + if case .connected = link.state { + statusItem.button?.appearsDisabled = false } else { - name = "keyboard.badge.ellipsis" + statusItem.button?.appearsDisabled = true } - statusItem.button?.image = NSImage(systemSymbolName: name, accessibilityDescription: "DroidBridge") + statusItem.button?.toolTip = statusText() } // MARK: - Menu diff --git a/mac/scripts/build-app.sh b/mac/scripts/build-app.sh index 77fea5c..af76e0e 100755 --- a/mac/scripts/build-app.sh +++ b/mac/scripts/build-app.sh @@ -20,6 +20,15 @@ mkdir -p "$APP/Contents/MacOS" "$APP/Contents/Resources" cp "$BIN" "$APP/Contents/MacOS/DroidBridge" cp "$JAR" "$APP/Contents/Resources/droidbridge-server.jar" cp -R Resources/*.lproj "$APP/Contents/Resources/" +cp Resources/MenuBarIcon.png Resources/MenuBarIcon@2x.png "$APP/Contents/Resources/" +# App icon: all the sizes macOS asks for, from the 1024 px master. +ICONSET=$(mktemp -d)/AppIcon.iconset +mkdir -p "$ICONSET" +for s in 16 32 128 256 512; do + sips -z $s $s Resources/AppIcon-1024.png --out "$ICONSET/icon_${s}x${s}.png" >/dev/null + sips -z $((s * 2)) $((s * 2)) Resources/AppIcon-1024.png --out "$ICONSET/icon_${s}x${s}@2x.png" >/dev/null +done +iconutil -c icns "$ICONSET" -o "$APP/Contents/Resources/AppIcon.icns" cat > "$APP/Contents/Info.plist" < @@ -30,6 +39,7 @@ cat > "$APP/Contents/Info.plist" <CFBundleDisplayNameDroidBridge CFBundleExecutableDroidBridge CFBundlePackageTypeAPPL + CFBundleIconFileAppIcon CFBundleShortVersionString$VERSION CFBundleVersion$VERSION CFBundleDevelopmentRegionen From 0c4f20dad08ece3ab45e164a2a97eeea8575e115 Mon Sep 17 00:00:00 2001 From: beyondlevi Date: Wed, 7 Oct 2026 11:27:49 -0300 Subject: [PATCH 11/11] =?UTF-8?q?Device=20can=20touch=20several=20displays?= =?UTF-8?q?=20(passages=20from=20its=20rectangle),=20return=20stretches=20?= =?UTF-8?q?per=20side,=20=C3=A7=20fix=20for=20US=20International?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Volund OS <287815646+volundos[bot]@users.noreply.github.com> --- mac/Resources/en.lproj/Localizable.strings | 5 +- mac/Resources/pt-BR.lproj/Localizable.strings | 5 +- mac/Sources/DroidBridge/AppDelegate.swift | 13 +- .../DroidBridge/ArrangementWindow.swift | 195 +++++++++--------- mac/Sources/DroidBridge/Displays.swift | 29 ++- mac/Sources/DroidBridge/InputCapture.swift | 51 ++++- mac/Sources/DroidBridge/Settings.swift | 18 +- mac/Sources/DroidBridgeCore/CedillaFix.swift | 52 +++++ .../DroidBridgeCore/EdgeGeometry.swift | 119 +++++++++-- mac/Sources/DroidBridgeCore/Wire.swift | 8 +- .../DroidBridgeCoreTests/CoreTests.swift | 84 +++++++- protocol/README.md | 5 +- server/src/dev/droidbridge/Protocol.java | 2 +- server/src/dev/droidbridge/Session.java | 111 +++++++--- 14 files changed, 495 insertions(+), 202 deletions(-) create mode 100644 mac/Sources/DroidBridgeCore/CedillaFix.swift diff --git a/mac/Resources/en.lproj/Localizable.strings b/mac/Resources/en.lproj/Localizable.strings index 69df3e7..72a3bcf 100644 --- a/mac/Resources/en.lproj/Localizable.strings +++ b/mac/Resources/en.lproj/Localizable.strings @@ -14,7 +14,7 @@ "menu.quit" = "Quit DroidBridge"; "menu.arrange" = "Arrange Screens…"; "arrangement.title" = "Arrange Screens"; -"arrangement.hint" = "Drag the Android device against an edge of a display. The pointer only passes through the highlighted stretch; along the rest of the edge it stops, as between displays of different sizes."; +"arrangement.hint" = "Drag the Android device against one or two displays, as in System Settings > Displays. The pointer only passes through the highlighted stretches; along the rest of the edges it stops."; "arrangement.passage" = "Passage"; "arrangement.size" = "Size of the Android device in the arrangement"; "arrangement.sizeHint" = "Larger means a longer passage."; @@ -29,4 +29,5 @@ "axis.width" = "width"; "passage.text" = "%1$@: %2$@ edge, from %3$d%% to %4$d%% of its %5$@."; "passage.drop" = "Drop to attach to %1$@: %2$@ edge, from %3$d%% to %4$d%% of its %5$@."; -"passage.enters" = "The pointer enters through the Android device's %@ edge at the matching position."; +"passage.enters" = "The pointer enters the Android device through the side that touches the display, at the matching position."; +"passage.dropNone" = "Not touching any display: dropping it here puts it back where it was."; diff --git a/mac/Resources/pt-BR.lproj/Localizable.strings b/mac/Resources/pt-BR.lproj/Localizable.strings index f795dd0..f2cf71c 100644 --- a/mac/Resources/pt-BR.lproj/Localizable.strings +++ b/mac/Resources/pt-BR.lproj/Localizable.strings @@ -14,7 +14,7 @@ "menu.quit" = "Sair do DroidBridge"; "menu.arrange" = "Organizar telas…"; "arrangement.title" = "Organizar telas"; -"arrangement.hint" = "Arraste o Android até encostar numa borda de um monitor. O ponteiro passa só pelo trecho destacado; no resto da borda ele para, como entre monitores de tamanhos diferentes."; +"arrangement.hint" = "Arraste o Android até encostar em um ou dois monitores, como em Ajustes do Sistema > Monitores. O ponteiro passa só pelos trechos destacados; no resto das bordas ele para."; "arrangement.passage" = "Passagem"; "arrangement.size" = "Tamanho do Android na organização"; "arrangement.sizeHint" = "Maior = trecho de passagem mais longo."; @@ -29,4 +29,5 @@ "axis.width" = "largura"; "passage.text" = "%1$@: borda %2$@, de %3$d%% a %4$d%% da %5$@."; "passage.drop" = "Solte para encostar em %1$@: borda %2$@, de %3$d%% a %4$d%% da %5$@."; -"passage.enters" = "O ponteiro entra pela borda %@ do Android, na posição correspondente."; +"passage.enters" = "O ponteiro entra no Android pelo lado que encosta no monitor, na posição correspondente."; +"passage.dropNone" = "Longe dos monitores: soltando aqui, ele volta para onde estava."; diff --git a/mac/Sources/DroidBridge/AppDelegate.swift b/mac/Sources/DroidBridge/AppDelegate.swift index 323b1a6..8cc5e4a 100644 --- a/mac/Sources/DroidBridge/AppDelegate.swift +++ b/mac/Sources/DroidBridge/AppDelegate.swift @@ -13,7 +13,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate { private var statusItem: NSStatusItem! private var tapTimer: Timer? private let arrangementWindow = ArrangementWindowController() - private var passage: Passage? + private var passages: [Passage] = [] private let log = Logger(subsystem: "dev.droidbridge", category: "app") func applicationDidFinishLaunching(_ notification: Notification) { @@ -29,7 +29,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate { return true } capture.onRemoteChanged = { [weak self] _ in self?.updateIcon() } - capture.passage = { [weak self] in self?.passage } + capture.passages = { [weak self] in self?.passages ?? [] } refreshPassage() arrangementWindow.model.onChange = { [weak self] in self?.refreshPassage() } NotificationCenter.default.addObserver(forName: NSApplication.didChangeScreenParametersNotification, object: nil, @@ -51,6 +51,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate { self.sendKeyboardLayout() self.arrangementWindow.model.deviceName = model self.arrangementWindow.model.deviceSize = CGSize(width: width, height: height) + self.refreshPassage() } else { self.capture.returnToMac() } @@ -58,7 +59,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate { } link.onMessage = { [weak self] message in switch message { - case let .edge(_, ratio): self?.capture.returnToMac(ratio: ratio) + case let .edge(side, ratio): self?.capture.returnToMac(side: side, ratio: ratio) case let .clipboard(text): self?.clipboard.receivedFromDevice(text) default: break } @@ -77,7 +78,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate { } private func refreshPassage() { - passage = Arrangements.passage(for: DisplayInfo.all()) + passages = Arrangements.passages(for: DisplayInfo.all(), aspect: arrangementWindow.model.aspect) } /// Makes the device's keyboard layout match the Mac's current one, so dead keys work the same. @@ -86,7 +87,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate { let source = TISCopyCurrentKeyboardLayoutInputSource()?.takeRetainedValue(), let raw = TISGetInputSourceProperty(source, kTISPropertyInputSourceID) else { return } let id = Unmanaged.fromOpaque(raw).takeUnretainedValue() as String - if let layout = KeyboardLayoutMap.androidLayout(forInputSource: id) { + let layout = KeyboardLayoutMap.androidLayout(forInputSource: id) + capture.cedilla.enabled = layout == "english_us_intl" + if let layout { log.info("keyboard layout \(id, privacy: .public) -> \(layout, privacy: .public)") link.send(Wire.layout(layout)) } else { diff --git a/mac/Sources/DroidBridge/ArrangementWindow.swift b/mac/Sources/DroidBridge/ArrangementWindow.swift index 3838279..166a7e3 100644 --- a/mac/Sources/DroidBridge/ArrangementWindow.swift +++ b/mac/Sources/DroidBridge/ArrangementWindow.swift @@ -2,13 +2,16 @@ import AppKit import DroidBridgeCore import SwiftUI -/// "Arrange screens": drag the Android device against an edge of a Mac display. The overlap is the -/// passage; the size slider sets how long it is. +/// "Arrange screens": drag the Android device against the edges of the Mac displays, like in the +/// macOS Displays settings. Every stretch of a display edge it touches is a passage; it can touch two +/// displays at once (in a corner between them). The size slider scales it. final class ArrangementModel: ObservableObject { @Published var displays: [DisplayInfo] = [] - @Published var arrangement: Arrangement? + @Published var device: CGRect? @Published var deviceName: String? - @Published var deviceSize: CGSize? + @Published var deviceSize: CGSize? { + didSet { if oldValue != deviceSize { reload() } } + } /// Drag offset of the device, in global points. @Published var drag: CGSize? @@ -18,73 +21,58 @@ final class ArrangementModel: ObservableObject { func reload() { displays = DisplayInfo.all() - arrangement = Arrangements.current(for: displays) - } - - var display: DisplayInfo? { - guard let a = arrangement else { return nil } - return displays.first { $0.key == a.displayKey } + device = Arrangements.deviceRect(for: displays, aspect: aspect) } - /// Device width / height as it is held now (portrait by default). + /// Device width / height as it is held now. var aspect: CGFloat { - guard let s = deviceSize, s.width > 0, s.height > 0 else { return 1080.0 / 2340.0 } + guard let s = deviceSize, s.width > 0, s.height > 0 else { return Arrangements.defaultAspect } return s.width / s.height } - /// The device rectangle, in global points, for an arrangement. - func deviceRect(_ a: Arrangement, on d: CGRect) -> CGRect { - let vertical = a.edge.isVertical - let length = CGFloat(a.end - a.start) * (vertical ? d.height : d.width) - let thickness = vertical ? length * aspect : length / aspect - switch a.edge { - case .right: return CGRect(x: d.maxX, y: d.minY + CGFloat(a.start) * d.height, width: thickness, height: length) - case .left: return CGRect(x: d.minX - thickness, y: d.minY + CGFloat(a.start) * d.height, width: thickness, height: length) - case .top: return CGRect(x: d.minX + CGFloat(a.start) * d.width, y: d.minY - thickness, width: length, height: thickness) - case .bottom: return CGRect(x: d.minX + CGFloat(a.start) * d.width, y: d.maxY, width: length, height: thickness) - } + var bounds: [CGRect] { displays.map(\.bounds) } + + var passages: [Passage] { + guard let device else { return [] } + return EdgeGeometry.passages(device: device, displays: bounds) } - /// Where the dragged device would attach, if it is close enough to a free edge. - func candidate(for ghost: CGRect, scale: CGFloat) -> Arrangement? { - guard let current = arrangement else { return nil } - let reach = Self.snapDistance / scale - var best: (Arrangement, CGFloat)? - for d in displays { - let r = d.bounds - for edge in DroidBridgeCore.Edge.allCases { - let gap: CGFloat - let center: CGFloat - switch edge { - case .right: gap = abs(ghost.minX - r.maxX); center = (ghost.midY - r.minY) / r.height - case .left: gap = abs(ghost.maxX - r.minX); center = (ghost.midY - r.minY) / r.height - case .top: gap = abs(ghost.maxY - r.minY); center = (ghost.midX - r.minX) / r.width - case .bottom: gap = abs(ghost.minY - r.maxY); center = (ghost.midX - r.minX) / r.width - } - guard gap <= reach, center > -0.15, center < 1.15 else { continue } - let free = EdgeGeometry.freeIntervals(of: r, edge: edge, displays: displays.map(\.bounds)) - guard let s = EdgeGeometry.snap(center: Double(center), length: current.size, free: free) else { continue } - let a = Arrangement(displayKey: d.key, edge: edge, start: s.lowerBound, end: s.upperBound, size: current.size) - if best == nil || gap < best!.1 { best = (a, gap) } - } - } - return best?.0 + func candidate(for ghost: CGRect, scale: CGFloat) -> CGRect? { + EdgeGeometry.snap(device: ghost, displays: bounds, reach: Self.snapDistance / scale) } - func commit(_ a: Arrangement) { - arrangement = a - Settings.arrangement = a + func commit(_ r: CGRect) { + device = r + Arrangements.save(r, displays: displays) onChange?() } - func setSize(_ size: Double) { - guard var a = arrangement, let d = display else { return } - let free = EdgeGeometry.freeIntervals(of: d.bounds, edge: a.edge, displays: displays.map(\.bounds)) - guard let s = EdgeGeometry.snap(center: (a.start + a.end) / 2, length: size, free: free) else { return } - a.size = size - a.start = s.lowerBound - a.end = s.upperBound - commit(a) + /// The slider: the device height relative to the display it touches first. + var size: Double { + guard let device, let d = passages.first?.display else { return 0.6 } + return Double(device.height / d.height) + } + + func setSize(_ value: Double) { + let ps = passages + guard let r = device, let d = ps.first?.display else { return } + let h = CGFloat(value) * d.height + let w = h * aspect + var x = r.midX - w / 2 + var y = r.midY - h / 2 + // Keep the sides that touch a display where they are. + for p in ps { + switch p.edge { + case .right: x = r.minX + case .left: x = r.maxX - w + case .top: y = r.maxY - h + case .bottom: y = r.minY + } + } + let next = CGRect(x: x, y: y, width: w, height: h) + let overlaps = bounds.contains { $0.intersection(next).width > 0.5 && $0.intersection(next).height > 0.5 } + guard !overlaps, !EdgeGeometry.passages(device: next, displays: bounds).isEmpty else { return } + commit(next) } func reset() { @@ -93,10 +81,10 @@ final class ArrangementModel: ObservableObject { onChange?() } - func describe(_ a: Arrangement, format: String) -> String { - let name = displays.first { $0.key == a.displayKey }?.name ?? "?" - return String(format: L(format), name, L("edge.\(a.edge.rawValue)"), Int((a.start * 100).rounded()), - Int((a.end * 100).rounded()), L(a.edge.isVertical ? "axis.height" : "axis.width")) + func describe(_ p: Passage, format: String = "passage.text") -> String { + let name = displays.first { $0.bounds == p.display }?.name ?? "?" + return String(format: L(format), name, L("edge.\(p.edge.rawValue)"), Int((p.start * 100).rounded()), + Int((p.end * 100).rounded()), L(p.edge.isVertical ? "axis.height" : "axis.width")) } } @@ -137,11 +125,11 @@ struct ArrangementView: View { Circle().fill(Self.accent).frame(width: 10, height: 10) Text(L("arrangement.passage")).font(.system(size: 13, weight: .semibold)) } - if let a = model.arrangement { - Text(model.describe(a, format: "passage.text")).font(.system(size: 13)) - Text(String(format: L("passage.enters"), L("edge.\(sideKey(a.edge.androidSide))"))) - .font(.system(size: 12)).foregroundColor(.secondary) + ForEach(Array(model.passages.enumerated()), id: \.offset) { _, p in + Text(model.describe(p)).font(.system(size: 13)).fixedSize(horizontal: false, vertical: true) } + Text(L("passage.enters")).font(.system(size: 12)).foregroundColor(.secondary) + .fixedSize(horizontal: false, vertical: true) } .frame(maxWidth: .infinity, alignment: .leading) .padding(EdgeInsets(top: 14, leading: 16, bottom: 14, trailing: 16)) @@ -152,7 +140,7 @@ struct ArrangementView: View { private var sizeCard: some View { VStack(alignment: .leading, spacing: 12) { Text(L("arrangement.size")).font(.system(size: 13, weight: .semibold)) - Slider(value: Binding(get: { model.arrangement?.size ?? 0.6 }, set: { model.setSize($0) }), in: 0.2...1) + Slider(value: Binding(get: { model.size }, set: { model.setSize($0) }), in: 0.2...1) Text(L("arrangement.sizeHint")).font(.system(size: 12)).foregroundColor(.secondary) } .frame(maxWidth: .infinity, alignment: .leading) @@ -160,15 +148,6 @@ struct ArrangementView: View { .background(RoundedRectangle(cornerRadius: 10).fill(Color.white)) .overlay(RoundedRectangle(cornerRadius: 10).stroke(Color(white: 0.878))) } - - private func sideKey(_ s: Side) -> String { - switch s { - case .left: return "left" - case .right: return "right" - case .top: return "top" - case .bottom: return "bottom" - } - } } /// The displays and the device, drawn to scale. @@ -178,37 +157,48 @@ private struct ArrangementCanvas: View { var body: some View { let t = transform() + let dragging = model.drag != nil + let candidate = model.drag.flatMap { drag in + model.device.flatMap { model.candidate(for: $0.offsetBy(dx: drag.width, dy: drag.height), scale: t.scale) } + } + let candidatePassages = candidate.map { EdgeGeometry.passages(device: $0, displays: model.bounds) } ?? [] ZStack(alignment: .topLeading) { ForEach(model.displays) { d in displayView(d).frame(width: d.bounds.width * t.scale, height: d.bounds.height * t.scale) .offset(t.point(d.bounds.origin)) } - if let a = model.arrangement, let d = model.display { - let rect = model.deviceRect(a, on: d.bounds) - let dragging = model.drag != nil - passageBar(a, on: d.bounds, t: t, color: dragging ? Color(white: 0.78) : ArrangementView.accent) - if let drag = model.drag, - let c = model.candidate(for: rect.offsetBy(dx: drag.width, dy: drag.height), scale: t.scale), - let cd = model.displays.first(where: { $0.key == c.displayKey }) { - passageBar(c, on: cd.bounds, t: t, color: ArrangementView.accent) - let target = model.deviceRect(c, on: cd.bounds) + if let rect = model.device { + ForEach(Array(model.passages.enumerated()), id: \.offset) { _, p in + passageBar(p, t: t, color: dragging ? Color(white: 0.78) : ArrangementView.accent) + } + if let c = candidate { + ForEach(Array(candidatePassages.enumerated()), id: \.offset) { _, p in + passageBar(p, t: t, color: ArrangementView.accent) + } RoundedRectangle(cornerRadius: 10) .stroke(ArrangementView.accent, style: StrokeStyle(lineWidth: 2, dash: [6, 4])) - .frame(width: target.width * t.scale, height: target.height * t.scale) - .offset(t.point(target.origin)) + .frame(width: c.width * t.scale, height: c.height * t.scale) + .offset(t.point(c.origin)) } // One view for both states, so the drag gesture survives the switch. let shown = model.drag.map { rect.offsetBy(dx: $0.width, dy: $0.height) } ?? rect deviceView(ghost: dragging).frame(width: shown.width * t.scale, height: shown.height * t.scale) .offset(t.point(shown.origin)) .gesture(dragGesture(rect: rect, t: t)) - if dragging, let drag = model.drag, - let c = model.candidate(for: rect.offsetBy(dx: drag.width, dy: drag.height), scale: t.scale) { - Text(model.describe(c, format: "passage.drop")) - .font(.system(size: 12, weight: .medium)) - .padding(EdgeInsets(top: 4, leading: 8, bottom: 4, trailing: 8)) - .background(Capsule().fill(Color.white.opacity(0.92))) - .offset(x: 10, y: 10) + if dragging { + VStack(alignment: .leading, spacing: 2) { + if candidatePassages.isEmpty { + Text(L("passage.dropNone")) + } else { + ForEach(Array(candidatePassages.enumerated()), id: \.offset) { _, p in + Text(model.describe(p, format: "passage.drop")) + } + } + } + .font(.system(size: 12, weight: .medium)) + .padding(EdgeInsets(top: 4, leading: 8, bottom: 4, trailing: 8)) + .background(RoundedRectangle(cornerRadius: 8).fill(Color.white.opacity(0.92))) + .offset(x: 10, y: 10) } } } @@ -263,20 +253,21 @@ private struct ArrangementCanvas: View { .contentShape(Rectangle()) } - private func passageBar(_ a: Arrangement, on d: CGRect, t: Transform, color: Color) -> some View { + private func passageBar(_ p: Passage, t: Transform, color: Color) -> some View { + let d = p.display let r: CGRect - switch a.edge { - case .right: r = CGRect(x: d.maxX, y: d.minY + CGFloat(a.start) * d.height, width: 0, height: CGFloat(a.end - a.start) * d.height) - case .left: r = CGRect(x: d.minX, y: d.minY + CGFloat(a.start) * d.height, width: 0, height: CGFloat(a.end - a.start) * d.height) - case .top: r = CGRect(x: d.minX + CGFloat(a.start) * d.width, y: d.minY, width: CGFloat(a.end - a.start) * d.width, height: 0) - case .bottom: r = CGRect(x: d.minX + CGFloat(a.start) * d.width, y: d.maxY, width: CGFloat(a.end - a.start) * d.width, height: 0) + switch p.edge { + case .right: r = CGRect(x: d.maxX, y: d.minY + CGFloat(p.start) * d.height, width: 0, height: CGFloat(p.end - p.start) * d.height) + case .left: r = CGRect(x: d.minX, y: d.minY + CGFloat(p.start) * d.height, width: 0, height: CGFloat(p.end - p.start) * d.height) + case .top: r = CGRect(x: d.minX + CGFloat(p.start) * d.width, y: d.minY, width: CGFloat(p.end - p.start) * d.width, height: 0) + case .bottom: r = CGRect(x: d.minX + CGFloat(p.start) * d.width, y: d.maxY, width: CGFloat(p.end - p.start) * d.width, height: 0) } - let p = t.point(r.origin) + let o = t.point(r.origin) let w = max(r.width * t.scale, 6) let h = max(r.height * t.scale, 6) return RoundedRectangle(cornerRadius: 3).fill(color) .frame(width: w, height: h) - .offset(x: p.width - (r.width == 0 ? 3 : 0), y: p.height - (r.height == 0 ? 3 : 0)) + .offset(x: o.width - (r.width == 0 ? 3 : 0), y: o.height - (r.height == 0 ? 3 : 0)) } struct Transform { diff --git a/mac/Sources/DroidBridge/Displays.swift b/mac/Sources/DroidBridge/Displays.swift index 73fd74b..db2cd72 100644 --- a/mac/Sources/DroidBridge/Displays.swift +++ b/mac/Sources/DroidBridge/Displays.swift @@ -29,17 +29,28 @@ struct DisplayInfo: Equatable, Identifiable { /// Resolves the saved arrangement against the displays connected now. enum Arrangements { - static func passage(for displays: [DisplayInfo]) -> Passage? { - if let a = Settings.arrangement, let d = displays.first(where: { $0.key == a.displayKey }) { - return Passage(display: d.bounds, edge: a.edge, start: a.start, end: a.end) + /// Device width / height while the size is unknown (a portrait phone). + static let defaultAspect: CGFloat = 1080.0 / 2340.0 + + /// The device rectangle in global points: the saved one when its anchor display is connected, + /// otherwise the default. + static func deviceRect(for displays: [DisplayInfo], aspect: CGFloat) -> CGRect? { + if let a = Settings.arrangement, let anchor = displays.first(where: { $0.key == a.anchorKey }) { + let r = a.rect(anchor: anchor.bounds) + if !EdgeGeometry.passages(device: r, displays: displays.map(\.bounds)).isEmpty { return r } } - return EdgeGeometry.defaultPassage(edge: Settings.legacyEdge, displays: displays.map(\.bounds)) + return EdgeGeometry.defaultDevice(displays: displays.map(\.bounds), aspect: aspect) + } + + static func passages(for displays: [DisplayInfo], aspect: CGFloat) -> [Passage] { + guard let r = deviceRect(for: displays, aspect: aspect) else { return [] } + return EdgeGeometry.passages(device: r, displays: displays.map(\.bounds)) } - /// The arrangement shown in the window: the saved one, or the default turned into one. - static func current(for displays: [DisplayInfo]) -> Arrangement? { - if let a = Settings.arrangement, displays.contains(where: { $0.key == a.displayKey }) { return a } - guard let p = passage(for: displays), let d = displays.first(where: { $0.bounds == p.display }) else { return nil } - return Arrangement(displayKey: d.key, edge: p.edge, start: p.start, end: p.end, size: p.end - p.start) + /// Saves a device rectangle, anchored to the first display it touches. + static func save(_ r: CGRect, displays: [DisplayInfo]) { + let ps = EdgeGeometry.passages(device: r, displays: displays.map(\.bounds)) + guard let first = ps.first, let anchor = displays.first(where: { $0.bounds == first.display }) else { return } + Settings.arrangement = Arrangement(anchorKey: anchor.key, rect: r, anchor: anchor.bounds) } } diff --git a/mac/Sources/DroidBridge/InputCapture.swift b/mac/Sources/DroidBridge/InputCapture.swift index fab7808..836b7b4 100644 --- a/mac/Sources/DroidBridge/InputCapture.swift +++ b/mac/Sources/DroidBridge/InputCapture.swift @@ -19,14 +19,17 @@ final class InputCapture { var onRemoteChanged: ((Bool) -> Void)? /// Whether crossing to the device is allowed (a device is connected and sharing is on). var canCross: () -> Bool = { false } - /// The passage to the device for the current displays (see Arrangements). - var passage: () -> Passage? = { nil } + /// The passages to the device for the current displays (see Arrangements). + var passages: () -> [Passage] = { [] } + /// Fixes ' + c for layouts where Android composes it differently (see CedillaFix). + var cedilla = CedillaFix() private(set) var isRemote = false private let log = Logger(subsystem: "dev.droidbridge", category: "input") private var tap: CFMachPort? private var source: CFRunLoopSource? private var crossed: Passage? + private var activePassages: [Passage] = [] /// After coming back, the cursor must move away from the edge before it can cross again. private var armed = true private var buttons: UInt8 = 0 @@ -64,13 +67,22 @@ final class InputCapture { } /// Brings control back to the Mac, e.g. when the device disconnects or with the hotkey. - func returnToMac(ratio: Double? = nil) { + func returnToMac(side: Side? = nil, ratio: Double? = nil) { guard isRemote else { return } isRemote = false send?(Wire.leave()) keyboard.reset() + cedilla.reset() buttons = 0 - if let passage = crossed { + // The passage leading out of that side of the device, at that position. + var passage = crossed + if let side, let ratio { + let candidates = activePassages.filter { $0.edge.androidSide == side } + passage = candidates.first { ratio >= $0.deviceStart - 0.01 && ratio <= $0.deviceEnd + 0.01 } + ?? candidates.min { abs(($0.deviceStart + $0.deviceEnd) / 2 - ratio) < abs(($1.deviceStart + $1.deviceEnd) / 2 - ratio) } + ?? crossed + } + if let passage { let p = EdgeGeometry.returnPoint(passage: passage, androidRatio: ratio ?? 0.5) CGWarpMouseCursorPosition(p) log.info("back on the Mac at \(p.x), \(p.y)") @@ -109,15 +121,19 @@ final class InputCapture { } return } - guard canCross(), let passage = passage(), - let ratio = EdgeGeometry.crossing(at: p, delta: delta, passage: passage, displays: Self.displays()) else { - return - } + guard canCross() else { return } + let all = passages() + let displays = Self.displays() + guard let (passage, ratio) = all.lazy.compactMap({ ps in + EdgeGeometry.crossing(at: p, delta: delta, passage: ps, displays: displays).map { (ps, $0) } + }).first else { return } isRemote = true crossed = passage + activePassages = all CGAssociateMouseAndMouseCursorPosition(0) cursor.hide() - send?(Wire.enter(side: passage.edge.androidSide, ratio: ratio)) + let returns = all.map { (side: $0.edge.androidSide, start: $0.deviceStart, end: $0.deviceEnd) } + send?(Wire.enter(side: passage.edge.androidSide, ratio: ratio, returns: returns)) log.info("to Android, ratio \(ratio)") onRemoteChanged?(true) } @@ -175,12 +191,25 @@ final class InputCapture { } guard let usage = KeyMap.usage(forKeyCode: code) else { return } var changed = keyboard.setModifiers(HIDModifiers.from(flags: event.flags, commandAsControl: options.commandAsControl)) + let actions: [CedillaFix.Action] if type == .keyDown { // Android repeats held keys itself; ignore macOS autorepeat. if event.getIntegerValueField(.keyboardEventAutorepeat) != 0 { return } - changed = keyboard.press(usage) || changed + actions = cedilla.keyDown(usage, modifiers: keyboard.modifiers) } else { - changed = keyboard.release(usage) || changed + actions = cedilla.keyUp(usage) + } + for action in actions { + switch action { + case let .press(u): + changed = keyboard.press(u) || changed + case let .release(u): + changed = keyboard.release(u) || changed + case let .tap(u, mods): + send?(Wire.keys([mods.rawValue, 0, u, 0, 0, 0, 0, 0])) + send?(Wire.keys([mods.rawValue, 0, 0, 0, 0, 0, 0, 0])) + changed = true + } } if changed { send?(Wire.keys(keyboard.report)) } } diff --git a/mac/Sources/DroidBridge/Settings.swift b/mac/Sources/DroidBridge/Settings.swift index b6026bb..978e377 100644 --- a/mac/Sources/DroidBridge/Settings.swift +++ b/mac/Sources/DroidBridge/Settings.swift @@ -10,28 +10,18 @@ struct Settings { set { d.set(newValue, forKey: "enabled") } } - /// Where the device sits; nil means the default (the outermost right edge). + /// Where the device sits; nil means the default (against the outermost right edge). static var arrangement: Arrangement? { - get { d.data(forKey: "arrangement").flatMap { try? JSONDecoder().decode(Arrangement.self, from: $0) } } + get { d.data(forKey: "deviceArrangement").flatMap { try? JSONDecoder().decode(Arrangement.self, from: $0) } } set { if let newValue, let data = try? JSONEncoder().encode(newValue) { - d.set(data, forKey: "arrangement") + d.set(data, forKey: "deviceArrangement") } else { - d.removeObject(forKey: "arrangement") + d.removeObject(forKey: "deviceArrangement") } } } - /// The side chosen in version 0.1 ("Android device position"), used until an arrangement is saved. - static var legacyEdge: Edge { - switch d.string(forKey: "placement") { - case "left": return .left - case "above": return .top - case "below": return .bottom - default: return .right - } - } - static var speed: Double { get { d.object(forKey: "speed") as? Double ?? 3.0 } set { d.set(newValue, forKey: "speed") } diff --git a/mac/Sources/DroidBridgeCore/CedillaFix.swift b/mac/Sources/DroidBridgeCore/CedillaFix.swift new file mode 100644 index 0000000..4529e8c --- /dev/null +++ b/mac/Sources/DroidBridgeCore/CedillaFix.swift @@ -0,0 +1,52 @@ +/// macOS "US International - PC" types ç for ' then c; Android's "English (US), International style" +/// composes ć instead and has ç on AltGr + comma. This holds the dead ' back and, when c follows, +/// sends AltGr + comma. +public struct CedillaFix { + public enum Action: Equatable { + case press(UInt8) + case release(UInt8) + /// Press and release `usage` with exactly these modifiers, then restore the held state. + case tap(UInt8, HIDModifiers) + } + + static let quote: UInt8 = 0x34 + static let c: UInt8 = 0x06 + static let comma: UInt8 = 0x36 + + public var enabled = false + private var pending = false + private var swallow: UInt8? + + public init() {} + + public mutating func keyDown(_ usage: UInt8, modifiers: HIDModifiers) -> [Action] { + guard enabled else { return [.press(usage)] } + if pending { + pending = false + if usage == Self.c, modifiers.isSubset(of: [.leftShift, .rightShift]) { + swallow = usage + return [.tap(Self.comma, modifiers.union(.rightAlt))] + } + return [.tap(Self.quote, []), .press(usage)] + } + if usage == Self.quote, modifiers.isEmpty { + pending = true + swallow = usage + return [] + } + return [.press(usage)] + } + + public mutating func keyUp(_ usage: UInt8) -> [Action] { + if swallow == usage { + swallow = nil + return [] + } + return [.release(usage)] + } + + public mutating func reset() { + pending = false + swallow = nil + } +} diff --git a/mac/Sources/DroidBridgeCore/EdgeGeometry.swift b/mac/Sources/DroidBridgeCore/EdgeGeometry.swift index 3170961..339b92a 100644 --- a/mac/Sources/DroidBridgeCore/EdgeGeometry.swift +++ b/mac/Sources/DroidBridgeCore/EdgeGeometry.swift @@ -19,39 +19,48 @@ public enum Edge: String, Codable, CaseIterable { } /// Where the pointer passes between the Mac and the device: a stretch of one display's edge. -/// `start` and `end` are fractions along the edge, from the top (vertical edges) or the left. +/// `start` and `end` are fractions along the display edge, from the top (vertical edges) or the left; +/// `deviceStart` and `deviceEnd` are the same stretch as fractions along the device's facing edge. public struct Passage: Equatable { public var display: CGRect public var edge: Edge public var start: Double public var end: Double + public var deviceStart: Double + public var deviceEnd: Double - public init(display: CGRect, edge: Edge, start: Double, end: Double) { + public init(display: CGRect, edge: Edge, start: Double, end: Double, deviceStart: Double = 0, deviceEnd: Double = 1) { self.display = display self.edge = edge self.start = start self.end = end + self.deviceStart = deviceStart + self.deviceEnd = deviceEnd } /// The edge length in points. public var edgeLength: CGFloat { edge.isVertical ? display.height : display.width } } -/// The saved arrangement: which display (by a stable key), which edge, which stretch. +/// The saved arrangement: the device rectangle, relative to the origin of an anchor display (by a +/// stable key), in global points. Passages are wherever it touches display edges. public struct Arrangement: Codable, Equatable { - public var displayKey: String - public var edge: Edge - public var start: Double - public var end: Double - /// How long the passage is relative to the edge, as set by the size slider. - public var size: Double + public var anchorKey: String + public var x: Double + public var y: Double + public var width: Double + public var height: Double - public init(displayKey: String, edge: Edge, start: Double, end: Double, size: Double) { - self.displayKey = displayKey - self.edge = edge - self.start = start - self.end = end - self.size = size + public init(anchorKey: String, rect: CGRect, anchor: CGRect) { + self.anchorKey = anchorKey + x = Double(rect.minX - anchor.minX) + y = Double(rect.minY - anchor.minY) + width = Double(rect.width) + height = Double(rect.height) + } + + public func rect(anchor: CGRect) -> CGRect { + CGRect(x: anchor.minX + CGFloat(x), y: anchor.minY + CGFloat(y), width: CGFloat(width), height: CGFloat(height)) } } @@ -93,15 +102,17 @@ public enum EdgeGeometry { let r = Double(along) guard touching, outward, r >= 0, r <= 1, r >= passage.start, r <= passage.end, passage.end > passage.start, !covered(beyond, displays) else { return nil } - return (r - passage.start) / (passage.end - passage.start) + let t = (r - passage.start) / (passage.end - passage.start) + return passage.deviceStart + t * (passage.deviceEnd - passage.deviceStart) } /// Where to put the Mac cursor when the pointer comes back at `androidRatio` along the device edge: /// just inside the passage. public static func returnPoint(passage: Passage, androidRatio: Double, inset: CGFloat = 3) -> CGPoint { let d = passage.display - let a = min(max(androidRatio, 0), 1) - let r = CGFloat(passage.start + a * (passage.end - passage.start)) + let span = passage.deviceEnd - passage.deviceStart + let t = span > 0 ? min(max((androidRatio - passage.deviceStart) / span, 0), 1) : 0.5 + let r = CGFloat(passage.start + t * (passage.end - passage.start)) switch passage.edge { case .right: return CGPoint(x: d.maxX - inset, y: min(d.minY + r * d.height, d.maxY - 1)) case .left: return CGPoint(x: d.minX + inset - 1, y: min(d.minY + r * d.height, d.maxY - 1)) @@ -181,6 +192,78 @@ public enum EdgeGeometry { return nil } + /// The passages of a device rectangle: every stretch of a display edge it touches. + public static func passages(device r: CGRect, displays: [CGRect], tolerance: CGFloat = 0.5) -> [Passage] { + var out: [Passage] = [] + for d in displays { + for edge in Edge.allCases { + let flush: Bool + let lo: CGFloat + let hi: CGFloat + switch edge { + case .right: flush = abs(r.minX - d.maxX) <= tolerance; lo = max(r.minY, d.minY); hi = min(r.maxY, d.maxY) + case .left: flush = abs(r.maxX - d.minX) <= tolerance; lo = max(r.minY, d.minY); hi = min(r.maxY, d.maxY) + case .top: flush = abs(r.maxY - d.minY) <= tolerance; lo = max(r.minX, d.minX); hi = min(r.maxX, d.maxX) + case .bottom: flush = abs(r.minY - d.maxY) <= tolerance; lo = max(r.minX, d.minX); hi = min(r.maxX, d.maxX) + } + guard flush, hi - lo > 1 else { continue } + let edgeOrigin = edge.isVertical ? d.minY : d.minX + let edgeLength = edge.isVertical ? d.height : d.width + let deviceOrigin = edge.isVertical ? r.minY : r.minX + let deviceLength = edge.isVertical ? r.height : r.width + let start = Double((lo - edgeOrigin) / edgeLength) + let end = Double((hi - edgeOrigin) / edgeLength) + for f in freeIntervals(of: d, edge: edge, displays: displays) { + let s = max(start, f.lowerBound) + let e = min(end, f.upperBound) + guard e - s > 0.005 else { continue } + func device(_ v: Double) -> Double { + Double((edgeOrigin + CGFloat(v) * edgeLength - deviceOrigin) / deviceLength) + } + out.append(Passage(display: d, edge: edge, start: s, end: e, deviceStart: device(s), deviceEnd: device(e))) + } + } + } + return out + } + + /// Snaps a dragged device rectangle against the nearest display edges (each axis on its own, so it + /// can sit in a corner between two displays). Returns nil when it would touch nothing or overlap a display. + public static func snap(device g: CGRect, displays: [CGRect], reach: CGFloat) -> CGRect? { + var dx: (CGFloat, CGFloat)? // (distance, shift) + var dy: (CGFloat, CGFloat)? + for d in displays { + if g.maxY > d.minY - reach, g.minY < d.maxY + reach { + for shift in [d.maxX - g.minX, d.minX - g.maxX] where abs(shift) <= reach { + if dx == nil || abs(shift) < dx!.0 { dx = (abs(shift), shift) } + } + } + if g.maxX > d.minX - reach, g.minX < d.maxX + reach { + for shift in [d.maxY - g.minY, d.minY - g.maxY] where abs(shift) <= reach { + if dy == nil || abs(shift) < dy!.0 { dy = (abs(shift), shift) } + } + } + } + var options: [CGRect] = [] + if let dx, let dy { options.append(g.offsetBy(dx: dx.1, dy: dy.1)) } + if let dx { options.append(g.offsetBy(dx: dx.1, dy: 0)) } + if let dy { options.append(g.offsetBy(dx: 0, dy: dy.1)) } + return options.first { r in + !displays.contains { $0.intersection(r).width > 0.5 && $0.intersection(r).height > 0.5 } + && !passages(device: r, displays: displays).isEmpty + } + } + + /// The default device rectangle: against the outermost display's free right edge, centered, + /// `size` times that display's height. + public static func defaultDevice(displays: [CGRect], aspect: CGFloat, size: CGFloat = 0.6) -> CGRect? { + guard let p = defaultPassage(edge: .right, displays: displays) else { return nil } + let d = p.display + let height = min(size * d.height, CGFloat(p.end - p.start) * d.height) + let center = d.minY + CGFloat(p.start + p.end) / 2 * d.height + return CGRect(x: d.maxX, y: center - height / 2, width: height * aspect, height: height) + } + static func covered(_ p: CGPoint, _ displays: [CGRect]) -> Bool { displays.contains { $0.contains(p) } } diff --git a/mac/Sources/DroidBridgeCore/Wire.swift b/mac/Sources/DroidBridgeCore/Wire.swift index f938640..bd1149e 100644 --- a/mac/Sources/DroidBridgeCore/Wire.swift +++ b/mac/Sources/DroidBridgeCore/Wire.swift @@ -31,8 +31,12 @@ public enum Wire { public static func hello() -> Data { frame(.hello, be16(version)) } - public static func enter(side: Side, ratio: Double) -> Data { - frame(.enter, [side.rawValue] + be16(ratio16(ratio))) + /// Puts the pointer on `side` at `ratio`; `returns` are the stretches of the device edges that + /// lead back to the Mac. + public static func enter(side: Side, ratio: Double, returns: [(side: Side, start: Double, end: Double)] = []) -> Data { + var p = [side.rawValue] + be16(ratio16(ratio)) + [UInt8(min(returns.count, 255))] + for r in returns.prefix(255) { p += [r.side.rawValue] + be16(ratio16(r.start)) + be16(ratio16(r.end)) } + return frame(.enter, p) } public static func leave() -> Data { frame(.leave) } diff --git a/mac/Tests/DroidBridgeCoreTests/CoreTests.swift b/mac/Tests/DroidBridgeCoreTests/CoreTests.swift index 8a9e5d3..f3ed5fb 100644 --- a/mac/Tests/DroidBridgeCoreTests/CoreTests.swift +++ b/mac/Tests/DroidBridgeCoreTests/CoreTests.swift @@ -9,7 +9,9 @@ final class WireTests: XCTestCase { } func testEnterFrame() { - XCTAssertEqual([UInt8](Wire.enter(side: .left, ratio: 1)), [0x02, 0, 0, 0, 3, 0, 0xFF, 0xFF]) + XCTAssertEqual([UInt8](Wire.enter(side: .left, ratio: 1)), [0x02, 0, 0, 0, 4, 0, 0xFF, 0xFF, 0]) + XCTAssertEqual([UInt8](Wire.enter(side: .left, ratio: 0, returns: [(side: .bottom, start: 0, end: 1)])), + [0x02, 0, 0, 0, 9, 0, 0, 0, 1, 3, 0, 0, 0xFF, 0xFF]) } func testReaderSplitsAndJoins() throws { @@ -113,6 +115,86 @@ final class EdgeTests: XCTestCase { } } +final class DeviceRectTests: XCTestCase { + let external = CGRect(x: 0, y: 0, width: 1920, height: 1080) + let builtin = CGRect(x: 1920, y: 494, width: 1920, height: 1243) + var displays: [CGRect] { [external, builtin] } + + func testCornerTouchesBothDisplays() { + // In the corner right of the external display and above the MacBook. + let r = CGRect(x: 1920, y: 94, width: 530, height: 400) + let ps = EdgeGeometry.passages(device: r, displays: displays) + XCTAssertEqual(ps.count, 2) + let right = ps.first { $0.edge == .right } + XCTAssertEqual(right?.display, external) + XCTAssertEqual(right?.start ?? -1, 94.0 / 1080, accuracy: 0.001) + XCTAssertEqual(right?.end ?? -1, 494.0 / 1080, accuracy: 0.001) + XCTAssertEqual(right?.deviceStart ?? -1, 0, accuracy: 0.001) + XCTAssertEqual(right?.deviceEnd ?? -1, 1, accuracy: 0.001) + let top = ps.first { $0.edge == .top } + XCTAssertEqual(top?.display, builtin) + XCTAssertEqual(top?.end ?? -1, 530.0 / 1920, accuracy: 0.001) + XCTAssertEqual(top?.edge.androidSide, .bottom) + } + + func testPartialPassageMapsToDevicePart() { + // Taller than the free stretch: only its lower part touches the MacBook's right edge. + let r = CGRect(x: 3840, y: 1437, width: 300, height: 600) + let ps = EdgeGeometry.passages(device: r, displays: displays) + XCTAssertEqual(ps.count, 1) + XCTAssertEqual(ps[0].deviceStart, 0, accuracy: 0.001) + XCTAssertEqual(ps[0].deviceEnd, 0.5, accuracy: 0.001) + let ratio = EdgeGeometry.crossing(at: CGPoint(x: 3839.5, y: 1587), delta: CGVector(dx: 1, dy: 0), passage: ps[0], displays: displays) + XCTAssertEqual(ratio ?? -1, 0.25, accuracy: 0.001) + let back = EdgeGeometry.returnPoint(passage: ps[0], androidRatio: 0.25) + XCTAssertEqual(back.y, 1587, accuracy: 0.5) + } + + func testSnapIntoCorner() { + let ghost = CGRect(x: 1930, y: 80, width: 530, height: 400) + let r = EdgeGeometry.snap(device: ghost, displays: displays, reach: 24) + XCTAssertEqual(r, CGRect(x: 1920, y: 94, width: 530, height: 400)) + } + + func testSnapRejectsOverlapAndFarAway() { + XCTAssertNil(EdgeGeometry.snap(device: CGRect(x: 100, y: 100, width: 300, height: 300), displays: displays, reach: 24)) + XCTAssertNil(EdgeGeometry.snap(device: CGRect(x: 5000, y: 100, width: 300, height: 300), displays: displays, reach: 24)) + } + + func testDefaultDevice() { + let r = EdgeGeometry.defaultDevice(displays: displays, aspect: 0.5) + XCTAssertEqual(r?.minX, 3840) + XCTAssertEqual(r?.height ?? 0, 0.6 * 1243, accuracy: 0.01) + XCTAssertEqual(EdgeGeometry.passages(device: r!, displays: displays).count, 1) + } +} + +final class CedillaTests: XCTestCase { + func testQuoteCBecomesCedilla() { + var f = CedillaFix() + f.enabled = true + XCTAssertEqual(f.keyDown(0x34, modifiers: []), []) + XCTAssertEqual(f.keyUp(0x34), []) + XCTAssertEqual(f.keyDown(0x06, modifiers: []), [.tap(0x36, .rightAlt)]) + XCTAssertEqual(f.keyUp(0x06), []) + XCTAssertEqual(f.keyDown(0x34, modifiers: []), []) + XCTAssertEqual(f.keyDown(0x06, modifiers: .leftShift), [.tap(0x36, [.leftShift, .rightAlt])]) + } + + func testOtherKeysKeepTheDeadKey() { + var f = CedillaFix() + f.enabled = true + _ = f.keyDown(0x34, modifiers: []) + XCTAssertEqual(f.keyDown(0x04, modifiers: []), [.tap(0x34, []), .press(0x04)]) + XCTAssertEqual(f.keyUp(0x04), [.release(0x04)]) + } + + func testDisabled() { + var f = CedillaFix() + XCTAssertEqual(f.keyDown(0x34, modifiers: []), [.press(0x34)]) + } +} + final class LayoutMapTests: XCTestCase { func testKnownLayouts() { XCTAssertEqual(KeyboardLayoutMap.androidLayout(forInputSource: "com.apple.keylayout.USInternational-PC"), "english_us_intl") diff --git a/protocol/README.md b/protocol/README.md index 2e2d0f2..ce079fb 100644 --- a/protocol/README.md +++ b/protocol/README.md @@ -19,18 +19,19 @@ All integers are big-endian. `side` is a side of the Android screen: | Type | Name | Payload | |---|---|---| | 0x01 | HELLO | u16 protocol version | -| 0x02 | ENTER | u8 side, u16 ratio: put the pointer on this edge | +| 0x02 | ENTER | u8 side, u16 ratio: put the pointer on this edge; then u8 n and n × (u8 side, u16 start, u16 end): the stretches of the device edges that lead back to the Mac (without them: the whole entry side) | | 0x03 | LEAVE | empty: release keys and buttons, stop watching the edge | | 0x04 | MOUSE | u8 buttons (bit 0 left, 1 right, 2 middle, 3 back, 4 forward), i16 dx, i16 dy, i8 wheel, i8 horizontal wheel | | 0x05 | KEYS | 8-byte HID boot keyboard report (modifiers, reserved, 6 usages) | | 0x06 | CLIPBOARD | UTF-8 text to put on the device clipboard | | 0x07 | PING | empty | +| 0x08 | LAYOUT | UTF-8 Android keyboard layout name, e.g. `english_us_intl` (Android 15+) | ## Android to Mac | Type | Name | Payload | |---|---|---| | 0x81 | DEVICE | u16 protocol version, u16 width, u16 height, UTF-8 model; sent first | -| 0x82 | EDGE | u8 side, u16 ratio: the pointer was pushed out of the return edge | +| 0x82 | EDGE | u8 side, u16 ratio: the pointer was pushed out through a return stretch | | 0x83 | CLIPBOARD | UTF-8 text copied on the device | | 0x84 | PONG | empty | diff --git a/server/src/dev/droidbridge/Protocol.java b/server/src/dev/droidbridge/Protocol.java index 2b65479..f4e3e0b 100644 --- a/server/src/dev/droidbridge/Protocol.java +++ b/server/src/dev/droidbridge/Protocol.java @@ -9,7 +9,7 @@ public final class Protocol { // Mac -> Android public static final int HELLO = 0x01; // u16 protocol version - public static final int ENTER = 0x02; // u8 side, u16 ratio along the edge (0..65535) + public static final int ENTER = 0x02; // u8 side, u16 ratio, u8 n, n x {u8 side, u16 start, u16 end} public static final int LEAVE = 0x03; // empty public static final int MOUSE = 0x04; // u8 buttons, i16 dx, i16 dy, i8 wheel, i8 hwheel public static final int KEYS = 0x05; // 8-byte HID boot keyboard report diff --git a/server/src/dev/droidbridge/Session.java b/server/src/dev/droidbridge/Session.java index 6e24b7a..3b63021 100644 --- a/server/src/dev/droidbridge/Session.java +++ b/server/src/dev/droidbridge/Session.java @@ -40,8 +40,10 @@ final class Session { private final ClipboardManager clipboard; private final ExecutorService prober = Executors.newSingleThreadExecutor(); - private volatile int returnSide = Protocol.SIDE_NONE; - private volatile boolean atEdge; + /** Stretches of the device edges that lead back to the Mac: {side, start, end}; null = none. */ + private volatile float[][] returns; + /** The side the pointer is touching inside a return stretch, or SIDE_NONE. */ + private volatile int edgeSide = Protocol.SIDE_NONE; private volatile float edgeRatio; private volatile boolean probing; private volatile long lastProbe; @@ -113,10 +115,10 @@ private void handle(int type, byte[] p) throws IOException { } break; case Protocol.ENTER: - enter(p[0], (((p[1] & 0xFF) << 8) | (p[2] & 0xFF)) / 65535f); + enter(p[0], u16(p, 1) / 65535f, parseReturns(p)); break; case Protocol.LEAVE: - returnSide = Protocol.SIDE_NONE; + returns = null; buttons = 0; mouse.input(new byte[5]); keyboard.input(new byte[8]); @@ -135,6 +137,24 @@ private void handle(int type, byte[] p) throws IOException { } } + private static int u16(byte[] p, int i) { + return ((p[i] & 0xFF) << 8) | (p[i + 1] & 0xFF); + } + + /** ENTER: u8 side, u16 ratio, then (protocol 1.1) u8 count and count x {u8 side, u16 start, u16 end}. */ + private static float[][] parseReturns(byte[] p) { + if (p.length < 4) { + return new float[][] {{p[0], 0, 1}}; + } + int count = p[3] & 0xFF; + float[][] out = new float[count][]; + for (int i = 0; i < count && 4 + i * 5 + 5 <= p.length; i++) { + int o = 4 + i * 5; + out[i] = new float[] {p[o], u16(p, o + 1) / 65535f, u16(p, o + 3) / 65535f}; + } + return out; + } + /** Sends a relative move as UHID reports, each axis limited to -127..127 per report. */ private void moveMouse(int dx, int dy, int wheel, int hwheel) throws IOException { do { @@ -156,9 +176,9 @@ private static int clamp(int v) { * Puts the pointer on the entry edge at the given ratio. The mouse is relative and Android * accelerates it, so: push into the corner, then walk along the edge and measure. */ - private void enter(int side, float ratio) throws IOException { - Log.i("enter side=" + side + " ratio=" + ratio); - returnSide = Protocol.SIDE_NONE; + private void enter(int side, float ratio, float[][] spans) throws IOException { + Log.i("enter side=" + side + " ratio=" + ratio + " returns=" + spans.length); + returns = null; boolean horizontalEdge = side == Protocol.SIDE_TOP || side == Protocol.SIDE_BOTTOM; int intoX = side == Protocol.SIDE_RIGHT ? 1 : -1; int intoY = side == Protocol.SIDE_BOTTOM ? 1 : -1; @@ -193,11 +213,11 @@ private void enter(int side, float ratio) throws IOException { } Log.i("entered at " + s + ", target " + target); } - atEdge = true; + edgeSide = side; edgeRatio = ratio; pushed = 0; edgeSent = false; - returnSide = side; + returns = spans; } /** Moves along one axis in small steps, so acceleration stays predictable. */ @@ -211,36 +231,45 @@ private void walk(boolean horizontal, int counts) throws IOException { } } - /** Watches for the pointer being pushed against the edge that leads back to the Mac. */ + private static int toward(int side, int dx, int dy) { + switch (side) { + case Protocol.SIDE_LEFT: return -dx; + case Protocol.SIDE_RIGHT: return dx; + case Protocol.SIDE_TOP: return -dy; + default: return dy; + } + } + + /** Watches for the pointer being pushed out through a stretch of edge that leads back to the Mac. */ private void onMotion(int dx, int dy) throws IOException { - int side = returnSide; - if (side == Protocol.SIDE_NONE || buttons != 0) { + float[][] spans = returns; + if (spans == null || spans.length == 0 || buttons != 0) { return; } - int toward; - switch (side) { - case Protocol.SIDE_LEFT: toward = -dx; break; - case Protocol.SIDE_RIGHT: toward = dx; break; - case Protocol.SIDE_TOP: toward = -dy; break; - default: toward = dy; break; + boolean anyToward = false; + for (float[] span : spans) { + if (span != null && toward((int) span[0], dx, dy) > 0) { + anyToward = true; + } } - if (toward <= 0) { + if (!anyToward) { if (dx != 0 || dy != 0) { - atEdge = false; + edgeSide = Protocol.SIDE_NONE; pushed = 0; } return; } - if (atEdge) { - pushed += toward; + int side = edgeSide; + if (side != Protocol.SIDE_NONE && toward(side, dx, dy) > 0) { + pushed += toward(side, dx, dy); if (pushed >= PUSH_TO_LEAVE && !edgeSent) { edgeSent = true; int r = Math.round(Math.max(0, Math.min(1, edgeRatio)) * 65535); send(Protocol.EDGE, new byte[] {(byte) side, (byte) (r >> 8), (byte) r}); - Log.i("edge reached, back to the Mac (ratio " + edgeRatio + ")"); + Log.i("edge reached on side " + side + ", back to the Mac (ratio " + edgeRatio + ")"); } - // Keep confirming while pushing: the pointer may have slid along the edge. } + // Keep probing while pushing: the pointer may have slid along the edge or out of the stretch. long now = System.currentTimeMillis(); if (!probing && now - lastProbe >= PROBE_INTERVAL_MS) { probing = true; @@ -249,16 +278,32 @@ private void onMotion(int dx, int dy) throws IOException { try { PointerProbe.Sample s = probe.sample(); if (s != null && s.width > 0) { - boolean touching; - float ratio; - switch (side) { - case Protocol.SIDE_LEFT: touching = s.x <= EDGE_SLOP; ratio = s.y / (s.height - 1); break; - case Protocol.SIDE_RIGHT: touching = s.x >= s.width - 1 - EDGE_SLOP; ratio = s.y / (s.height - 1); break; - case Protocol.SIDE_TOP: touching = s.y <= EDGE_SLOP; ratio = s.x / (s.width - 1); break; - default: touching = s.y >= s.height - 1 - EDGE_SLOP; ratio = s.x / (s.width - 1); break; + int found = Protocol.SIDE_NONE; + float foundRatio = 0; + for (float[] span : spans) { + if (span == null) { + continue; + } + int sd = (int) span[0]; + boolean touching; + float ratio; + switch (sd) { + case Protocol.SIDE_LEFT: touching = s.x <= EDGE_SLOP; ratio = s.y / (s.height - 1); break; + case Protocol.SIDE_RIGHT: touching = s.x >= s.width - 1 - EDGE_SLOP; ratio = s.y / (s.height - 1); break; + case Protocol.SIDE_TOP: touching = s.y <= EDGE_SLOP; ratio = s.x / (s.width - 1); break; + default: touching = s.y >= s.height - 1 - EDGE_SLOP; ratio = s.x / (s.width - 1); break; + } + if (touching && ratio >= span[1] - 0.01f && ratio <= span[2] + 0.01f) { + found = sd; + foundRatio = ratio; + break; + } + } + if (found != edgeSide) { + pushed = 0; } - edgeRatio = ratio; - atEdge = touching; + edgeRatio = foundRatio; + edgeSide = found; } } catch (IOException e) { Log.w("probe: " + e.getMessage());

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%0Ady11u&Y*JslU4;Q-v-yW(8rcx=<~;@ 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 u~WALg}Iy41UpA!o3B`@hIN)$5?N@6;o|r5}2U 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