diff --git a/app/src/control_msg.c b/app/src/control_msg.c index b3da5fe5..809255af 100644 --- a/app/src/control_msg.c +++ b/app/src/control_msg.c @@ -157,6 +157,9 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, uint8_t *buf) { sc_write16be(&buf[3], msg->uhid_input.size); memcpy(&buf[5], msg->uhid_input.data, msg->uhid_input.size); return 5 + msg->uhid_input.size; + case SC_CONTROL_MSG_TYPE_SET_CAMERA_TORCH: + buf[1] = msg->set_camera_torch.enabled ? 1 : 0; + return 2; case SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL: case SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL: case SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS: @@ -274,6 +277,10 @@ sc_control_msg_log(const struct sc_control_msg *msg) { case SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS: LOG_CMSG("open hard keyboard settings"); break; + case SC_CONTROL_MSG_TYPE_SET_CAMERA_TORCH: + LOG_CMSG("%s camera torch", + msg->set_camera_torch.enabled ? "enable" : "disable"); + break; default: LOG_CMSG("unknown type: %u", (unsigned) msg->type); break; diff --git a/app/src/control_msg.h b/app/src/control_msg.h index cd1340ef..9ea93547 100644 --- a/app/src/control_msg.h +++ b/app/src/control_msg.h @@ -41,6 +41,7 @@ enum sc_control_msg_type { SC_CONTROL_MSG_TYPE_UHID_CREATE, SC_CONTROL_MSG_TYPE_UHID_INPUT, SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS, + SC_CONTROL_MSG_TYPE_SET_CAMERA_TORCH, }; enum sc_screen_power_mode { @@ -106,6 +107,9 @@ struct sc_control_msg { uint16_t size; uint8_t data[SC_HID_MAX_SIZE]; } uhid_input; + struct { + bool enabled; + } set_camera_torch; }; }; diff --git a/app/src/input_manager.c b/app/src/input_manager.c index f26c4164..2aafa84b 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -330,6 +330,19 @@ open_hard_keyboard_settings(struct sc_input_manager *im) { } } +static void +set_camera_torch(struct sc_input_manager *im, bool enabled) { + assert(im->controller); + + struct sc_control_msg msg; + msg.type = SC_CONTROL_MSG_TYPE_SET_CAMERA_TORCH; + msg.set_camera_torch.enabled = enabled; + + if (!sc_controller_push_msg(im->controller, &msg)) { + LOGW("Could not request setting camera torch"); + } +} + static void apply_orientation_transform(struct sc_input_manager *im, enum sc_orientation transform) { @@ -569,6 +582,11 @@ sc_input_manager_process_key(struct sc_input_manager *im, open_hard_keyboard_settings(im); } return; + case SDLK_l: + if (control && !repeat && down) { + set_camera_torch(im, !shift); + } + return; } return; diff --git a/server/src/main/java/com/genymobile/scrcpy/CameraCapture.java b/server/src/main/java/com/genymobile/scrcpy/CameraCapture.java index a1003829..00faabca 100644 --- a/server/src/main/java/com/genymobile/scrcpy/CameraCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/CameraCapture.java @@ -53,6 +53,8 @@ public class CameraCapture extends SurfaceCapture { private final AtomicBoolean disconnected = new AtomicBoolean(); + private CameraCaptureSession currentSession; + public CameraCapture(String explicitCameraId, CameraFacing cameraFacing, Size explicitSize, int maxSize, CameraAspectRatio aspectRatio, int fps, boolean highSpeed) { this.explicitCameraId = explicitCameraId; @@ -196,7 +198,22 @@ public class CameraCapture extends SurfaceCapture { public void start(Surface surface) throws IOException { try { CameraCaptureSession session = createCaptureSession(cameraDevice, surface); - CaptureRequest request = createCaptureRequest(surface); + CaptureRequest request = createCaptureRequest(surface, false); + setRepeatingRequest(session, request); + setCurrentSession(session); + } catch (CameraAccessException | InterruptedException e) { + throw new IOException(e); + } + } + + @TargetApi(Build.VERSION_CODES.M) + public void setTorchEnabled(boolean enabled) throws IOException { + CameraCaptureSession session = getCurrentSession(); + if (session == null) { + return; + } + try { + CaptureRequest request = createCaptureRequest(session.getInputSurface(), enabled); setRepeatingRequest(session, request); } catch (CameraAccessException | InterruptedException e) { throw new IOException(e); @@ -310,9 +327,12 @@ public class CameraCapture extends SurfaceCapture { } } - private CaptureRequest createCaptureRequest(Surface surface) throws CameraAccessException { + private CaptureRequest createCaptureRequest(Surface surface, boolean torch) throws CameraAccessException { CaptureRequest.Builder requestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD); requestBuilder.addTarget(surface); + if (torch) { + requestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_TORCH); + } if (fps > 0) { requestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, new Range<>(fps, fps)); @@ -348,4 +368,12 @@ public class CameraCapture extends SurfaceCapture { public boolean isClosed() { return disconnected.get(); } + + private synchronized void setCurrentSession(CameraCaptureSession session) { + currentSession = session; + } + + private synchronized CameraCaptureSession getCurrentSession() { + return currentSession; + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java index bcbacb4b..fa7b4afc 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java @@ -20,6 +20,7 @@ public final class ControlMessage { public static final int TYPE_UHID_CREATE = 12; public static final int TYPE_UHID_INPUT = 13; public static final int TYPE_OPEN_HARD_KEYBOARD_SETTINGS = 14; + public static final int TYPE_SET_CAMERA_TORCH = 15; public static final long SEQUENCE_INVALID = 0; @@ -45,6 +46,7 @@ public final class ControlMessage { private long sequence; private int id; private byte[] data; + private boolean enabled; private ControlMessage() { } @@ -144,6 +146,13 @@ public final class ControlMessage { return msg; } + public static ControlMessage createSetCameraTorch(boolean enabled) { + ControlMessage msg = new ControlMessage(); + msg.type = TYPE_SET_CAMERA_TORCH; + msg.enabled = enabled; + return msg; + } + public int getType() { return type; } @@ -215,4 +224,8 @@ public final class ControlMessage { public byte[] getData() { return data; } + + public boolean getEnabled() { + return enabled; + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java index 1761d228..0f42dfaa 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java @@ -17,6 +17,7 @@ public class ControlMessageReader { static final int SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH = 9; static final int UHID_CREATE_FIXED_PAYLOAD_LENGTH = 4; static final int UHID_INPUT_FIXED_PAYLOAD_LENGTH = 4; + static final int CAMERA_TORCH_PAYLOAD_LENGTH = 1; private static final int MESSAGE_MAX_SIZE = 1 << 18; // 256k @@ -95,6 +96,9 @@ public class ControlMessageReader { case ControlMessage.TYPE_UHID_INPUT: msg = parseUhidInput(); break; + case ControlMessage.TYPE_SET_CAMERA_TORCH: + msg = parseCameraTorch(); + break; default: Ln.w("Unknown event type: " + type); msg = null; @@ -245,6 +249,14 @@ public class ControlMessageReader { return ControlMessage.createUhidInput(id, data); } + private ControlMessage parseCameraTorch() { + if (buffer.remaining() < CAMERA_TORCH_PAYLOAD_LENGTH) { + return null; + } + boolean enabled = buffer.get() != 0; + return ControlMessage.createSetCameraTorch(enabled); + } + private static Position readPosition(ByteBuffer buffer) { int x = buffer.getInt(); int y = buffer.getInt(); diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index 87faf8ba..6eee6d74 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -32,6 +32,7 @@ public class Controller implements AsyncProcessor { private final Device device; private final ControlChannel controlChannel; + private final CameraCapture cameraCapture; private final CleanUp cleanUp; private final DeviceMessageSender sender; private final boolean clipboardAutosync; @@ -46,9 +47,11 @@ public class Controller implements AsyncProcessor { private boolean keepPowerModeOff; - public Controller(Device device, ControlChannel controlChannel, CleanUp cleanUp, boolean clipboardAutosync, boolean powerOn) { + public Controller(Device device, ControlChannel controlChannel, CameraCapture cameraCapture, CleanUp cleanUp, boolean clipboardAutosync, + boolean powerOn) { this.device = device; this.controlChannel = controlChannel; + this.cameraCapture = cameraCapture; this.cleanUp = cleanUp; this.clipboardAutosync = clipboardAutosync; this.powerOn = powerOn; @@ -145,7 +148,7 @@ public class Controller implements AsyncProcessor { // this is expected on close return false; } - +Ln.i("==== msg = " + msg.getType()); switch (msg.getType()) { case ControlMessage.TYPE_INJECT_KEYCODE: if (device.supportsInputEvents()) { @@ -213,6 +216,10 @@ public class Controller implements AsyncProcessor { case ControlMessage.TYPE_OPEN_HARD_KEYBOARD_SETTINGS: openHardKeyboardSettings(); break; + case ControlMessage.TYPE_SET_CAMERA_TORCH: + Ln.i("==="); + cameraCapture.setTorchEnabled(msg.getEnabled()); + break; default: // do nothing } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 4763905c..76998ad2 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -2,6 +2,7 @@ package com.genymobile.scrcpy; import android.os.BatteryManager; import android.os.Build; +import android.os.SystemClock; import java.io.File; import java.io.IOException; @@ -144,6 +145,7 @@ public final class Server { asyncProcessors.add(audioRecorder); } + CameraCapture cameraCapture = null; if (video) { Streamer videoStreamer = new Streamer(connection.getVideoFd(), options.getVideoCodec(), options.getSendCodecMeta(), options.getSendFrameMeta()); @@ -151,8 +153,9 @@ public final class Server { if (options.getVideoSource() == VideoSource.DISPLAY) { surfaceCapture = new ScreenCapture(device); } else { - surfaceCapture = new CameraCapture(options.getCameraId(), options.getCameraFacing(), options.getCameraSize(), + cameraCapture = new CameraCapture(options.getCameraId(), options.getCameraFacing(), options.getCameraSize(), options.getMaxSize(), options.getCameraAspectRatio(), options.getCameraFps(), options.getCameraHighSpeed()); + surfaceCapture = cameraCapture; } SurfaceEncoder surfaceEncoder = new SurfaceEncoder(surfaceCapture, videoStreamer, options.getVideoBitRate(), options.getMaxFps(), options.getVideoCodecOptions(), options.getVideoEncoder(), options.getDownsizeOnError()); @@ -161,7 +164,8 @@ public final class Server { if (control) { ControlChannel controlChannel = connection.getControlChannel(); - Controller controller = new Controller(device, controlChannel, cleanUp, options.getClipboardAutosync(), options.getPowerOn()); + Controller controller = new Controller( + device, controlChannel, cameraCapture, cleanUp, options.getClipboardAutosync(), options.getPowerOn()); device.setClipboardListener(text -> { DeviceMessage msg = DeviceMessage.createClipboard(text); controller.getSender().send(msg);