mirror of
https://github.com/Genymobile/scrcpy.git
synced 2025-12-16 21:14:20 +01:00
Add shortcuts to switch the camera torch
MOD+l turns on the camera torch, MOD+Shift+l turns it off. TODO ref 6243 Co-authored-by: Tommie <teh420@gmail.com>
This commit is contained in:
@@ -819,6 +819,14 @@ Install APK from computer
|
||||
.B Drag & drop non-APK file
|
||||
Push file to device (see \fB\-\-push\-target\fR)
|
||||
|
||||
.TP
|
||||
.B MOD+l
|
||||
Turn on the camera torch (camera mode only)
|
||||
|
||||
.TP
|
||||
.B MOD+Shift+l
|
||||
Turn off the camera torch (camera mode only)
|
||||
|
||||
|
||||
.SH Environment variables
|
||||
|
||||
|
||||
@@ -1213,6 +1213,14 @@ static const struct sc_shortcut shortcuts[] = {
|
||||
.shortcuts = { "Drag & drop non-APK file" },
|
||||
.text = "Push file to device (see --push-target)",
|
||||
},
|
||||
{
|
||||
.shortcuts = { "MOD+l" },
|
||||
.text = "Turn on the camera torch (camera mode only)",
|
||||
},
|
||||
{
|
||||
.shortcuts = { "MOD+Shift+l" },
|
||||
.text = "Turn off the camera torch (camera mode only)",
|
||||
},
|
||||
};
|
||||
|
||||
static const struct sc_envvar envvars[] = {
|
||||
|
||||
@@ -182,6 +182,9 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, uint8_t *buf) {
|
||||
size_t len = write_string_tiny(&buf[1], msg->start_app.name, 255);
|
||||
return 1 + len;
|
||||
}
|
||||
case SC_CONTROL_MSG_TYPE_CAMERA_SET_TORCH:
|
||||
buf[1] = msg->camera_set_torch.on ? 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:
|
||||
|
||||
@@ -43,6 +43,7 @@ enum sc_control_msg_type {
|
||||
SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS,
|
||||
SC_CONTROL_MSG_TYPE_START_APP,
|
||||
SC_CONTROL_MSG_TYPE_RESET_VIDEO,
|
||||
SC_CONTROL_MSG_TYPE_CAMERA_SET_TORCH,
|
||||
};
|
||||
|
||||
enum sc_copy_key {
|
||||
@@ -111,6 +112,9 @@ struct sc_control_msg {
|
||||
struct {
|
||||
char *name;
|
||||
} start_app;
|
||||
struct {
|
||||
bool on;
|
||||
} camera_set_torch;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -302,6 +302,19 @@ reset_video(struct sc_input_manager *im) {
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
camera_set_torch(struct sc_input_manager *im, bool on) {
|
||||
assert(im->controller && im->camera);
|
||||
|
||||
struct sc_control_msg msg;
|
||||
msg.type = SC_CONTROL_MSG_TYPE_CAMERA_SET_TORCH;
|
||||
msg.camera_set_torch.on = on;
|
||||
|
||||
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) {
|
||||
@@ -572,6 +585,16 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
||||
}
|
||||
}
|
||||
|
||||
if (control && im->camera) {
|
||||
switch (sdl_keycode) {
|
||||
case SDLK_L:
|
||||
if (!repeat && down) {
|
||||
camera_set_torch(im, !shift);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -446,6 +446,25 @@ static void test_serialize_reset_video(void) {
|
||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||
}
|
||||
|
||||
static void test_serialize_camera_set_torch(void) {
|
||||
struct sc_control_msg msg = {
|
||||
.type = SC_CONTROL_MSG_TYPE_CAMERA_SET_TORCH,
|
||||
.camera_set_torch = {
|
||||
.on = true,
|
||||
},
|
||||
};
|
||||
|
||||
uint8_t buf[SC_CONTROL_MSG_MAX_SIZE];
|
||||
size_t size = sc_control_msg_serialize(&msg, buf);
|
||||
assert(size == 2);
|
||||
|
||||
const uint8_t expected[] = {
|
||||
SC_CONTROL_MSG_TYPE_CAMERA_SET_TORCH,
|
||||
0x01, // true
|
||||
};
|
||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
(void) argc;
|
||||
(void) argv;
|
||||
@@ -470,5 +489,6 @@ int main(int argc, char *argv[]) {
|
||||
test_serialize_open_hard_keyboard();
|
||||
test_serialize_start_app();
|
||||
test_serialize_reset_video();
|
||||
test_serialize_camera_set_torch();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ public final class ControlMessage {
|
||||
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 long SEQUENCE_INVALID = 0;
|
||||
|
||||
@@ -166,6 +167,13 @@ public final class ControlMessage {
|
||||
return msg;
|
||||
}
|
||||
|
||||
public static ControlMessage createCameraSetTorch(boolean on) {
|
||||
ControlMessage msg = new ControlMessage();
|
||||
msg.type = TYPE_CAMERA_SET_TORCH;
|
||||
msg.on = on;
|
||||
return msg;
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@@ -56,6 +56,8 @@ public class ControlMessageReader {
|
||||
return parseUhidDestroy();
|
||||
case ControlMessage.TYPE_START_APP:
|
||||
return parseStartApp();
|
||||
case ControlMessage.TYPE_CAMERA_SET_TORCH:
|
||||
return parseCameraSetTorch();
|
||||
default:
|
||||
throw new ControlProtocolException("Unknown event type: " + type);
|
||||
}
|
||||
@@ -166,6 +168,11 @@ public class ControlMessageReader {
|
||||
return ControlMessage.createStartApp(name);
|
||||
}
|
||||
|
||||
private ControlMessage parseCameraSetTorch() throws IOException {
|
||||
boolean on = dis.readBoolean();
|
||||
return ControlMessage.createCameraSetTorch(on);
|
||||
}
|
||||
|
||||
private Position parsePosition() throws IOException {
|
||||
int x = dis.readInt();
|
||||
int y = dis.readInt();
|
||||
|
||||
@@ -12,6 +12,7 @@ import com.genymobile.scrcpy.device.Position;
|
||||
import com.genymobile.scrcpy.device.Size;
|
||||
import com.genymobile.scrcpy.util.Ln;
|
||||
import com.genymobile.scrcpy.util.LogUtils;
|
||||
import com.genymobile.scrcpy.video.CameraCapture;
|
||||
import com.genymobile.scrcpy.video.SurfaceCapture;
|
||||
import com.genymobile.scrcpy.video.VideoSource;
|
||||
import com.genymobile.scrcpy.video.VirtualDisplayListener;
|
||||
@@ -99,7 +100,7 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
|
||||
|
||||
private boolean keepDisplayPowerOff;
|
||||
|
||||
// Used for resetting video encoding on RESET_VIDEO message
|
||||
// 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) {
|
||||
@@ -365,6 +366,16 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
|
||||
default:
|
||||
// fall through
|
||||
}
|
||||
} else {
|
||||
switch (type) {
|
||||
case ControlMessage.TYPE_CAMERA_SET_TORCH:
|
||||
assert surfaceCapture instanceof CameraCapture;
|
||||
CameraCapture cameraCapture = (CameraCapture) surfaceCapture;
|
||||
cameraCapture.setTorchEnabled(msg.getOn());
|
||||
return true;
|
||||
default:
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
|
||||
throw new AssertionError("Unexpected message type: " + type);
|
||||
|
||||
@@ -80,8 +80,10 @@ public class CameraCapture extends SurfaceCapture {
|
||||
|
||||
private final AtomicBoolean disconnected = new AtomicBoolean();
|
||||
|
||||
// Must be accessed only from the camera thread
|
||||
// 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();
|
||||
@@ -287,7 +289,7 @@ public class CameraCapture extends SurfaceCapture {
|
||||
}
|
||||
|
||||
try {
|
||||
CaptureRequest.Builder requestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
|
||||
requestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
|
||||
requestBuilder.addTarget(captureSurface);
|
||||
|
||||
if (fps > 0) {
|
||||
@@ -299,6 +301,7 @@ public class CameraCapture extends SurfaceCapture {
|
||||
|
||||
CaptureRequest request = requestBuilder.build();
|
||||
setRepeatingRequest(session, request);
|
||||
currentSession = session;
|
||||
} catch (CameraAccessException e) {
|
||||
Ln.e("Camera error", e);
|
||||
invalidate();
|
||||
@@ -324,6 +327,8 @@ public class CameraCapture extends SurfaceCapture {
|
||||
public void stop() {
|
||||
cameraHandler.post(() -> {
|
||||
assertCameraThread();
|
||||
currentSession = null;
|
||||
requestBuilder = null;
|
||||
started = false;
|
||||
});
|
||||
|
||||
@@ -434,6 +439,21 @@ public class CameraCapture extends SurfaceCapture {
|
||||
return disconnected.get();
|
||||
}
|
||||
|
||||
public void setTorchEnabled(boolean enabled) {
|
||||
cameraHandler.post(() -> {
|
||||
assertCameraThread();
|
||||
if (currentSession != null && requestBuilder != null) {
|
||||
try {
|
||||
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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void assertCameraThread() {
|
||||
assert Thread.currentThread() == cameraThread;
|
||||
}
|
||||
|
||||
@@ -422,6 +422,24 @@ public class ControlMessageReaderTest {
|
||||
Assert.assertEquals(-1, bis.read()); // EOS
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseCameraSetTorch() throws IOException {
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
DataOutputStream dos = new DataOutputStream(bos);
|
||||
dos.writeByte(ControlMessage.TYPE_CAMERA_SET_TORCH);
|
||||
dos.writeBoolean(true);
|
||||
byte[] packet = bos.toByteArray();
|
||||
|
||||
ByteArrayInputStream bis = new ByteArrayInputStream(packet);
|
||||
ControlMessageReader reader = new ControlMessageReader(bis);
|
||||
|
||||
ControlMessage event = reader.read();
|
||||
Assert.assertEquals(ControlMessage.TYPE_CAMERA_SET_TORCH, event.getType());
|
||||
Assert.assertTrue(event.getOn());
|
||||
|
||||
Assert.assertEquals(-1, bis.read()); // EOS
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultiEvents() throws IOException {
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
|
||||
Reference in New Issue
Block a user