Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions app/src/control_msg.c
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,11 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, uint8_t *buf) {
sc_write16be(&buf[1], msg->resize_display.width);
sc_write16be(&buf[3], msg->resize_display.height);
return 5;
case SC_CONTROL_MSG_TYPE_SCAN_FILE: {
size_t len = write_string(&buf[1], msg->scan_file.path,
SC_CONTROL_MSG_INJECT_TEXT_MAX_LENGTH);
return 1 + len;
}
case SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
case SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL:
case SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS:
Expand Down Expand Up @@ -341,6 +346,9 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
case SC_CONTROL_MSG_TYPE_CAMERA_ZOOM_OUT:
LOG_CMSG("camera zoom out");
break;
case SC_CONTROL_MSG_TYPE_SCAN_FILE:
LOG_CMSG("scan file \"%s\"", msg->scan_file.path);
break;
default:
LOG_CMSG("unknown type: %u", (unsigned) msg->type);
break;
Expand Down Expand Up @@ -369,6 +377,9 @@ sc_control_msg_destroy(struct sc_control_msg *msg) {
case SC_CONTROL_MSG_TYPE_START_APP:
free(msg->start_app.name);
break;
case SC_CONTROL_MSG_TYPE_SCAN_FILE:
free(msg->scan_file.path);
break;
default:
// do nothing
break;
Expand Down
4 changes: 4 additions & 0 deletions app/src/control_msg.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ enum sc_control_msg_type {
SC_CONTROL_MSG_TYPE_CAMERA_ZOOM_IN,
SC_CONTROL_MSG_TYPE_CAMERA_ZOOM_OUT,
SC_CONTROL_MSG_TYPE_RESIZE_DISPLAY,
SC_CONTROL_MSG_TYPE_SCAN_FILE,
};

enum sc_copy_key {
Expand Down Expand Up @@ -122,6 +123,9 @@ struct sc_control_msg {
uint16_t width;
uint16_t height;
} resize_display;
struct {
char *path; // owned, to be freed by free()
} scan_file;
};
};

Expand Down
55 changes: 48 additions & 7 deletions app/src/file_pusher.c
Original file line number Diff line number Diff line change
@@ -1,23 +1,55 @@
#include "file_pusher.h"

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "adb/adb.h"
#include "control_msg.h"
#include "controller.h"
#include "util/log.h"

#define DEFAULT_PUSH_TARGET "/sdcard/Download/"

static const char *
basename_of(const char *path) {
const char *sep = strrchr(path, '/');
#ifdef _WIN32
const char *bsl = strrchr(path, '\\');
if (bsl && (!sep || bsl > sep)) {
sep = bsl;
}
#endif
return sep ? sep + 1 : path;
}

static char *
build_remote_path(const char *target, const char *local_file) {
const char *base = basename_of(local_file);
size_t target_len = strlen(target);
bool needs_sep = target_len == 0 || target[target_len - 1] != '/';
size_t total = target_len + (needs_sep ? 1 : 0) + strlen(base) + 1;
char *result = malloc(total);
if (!result) {
LOG_OOM();
return NULL;
}
snprintf(result, total, "%s%s%s", target, needs_sep ? "/" : "", base);
return result;
}

static void
sc_file_pusher_request_destroy(struct sc_file_pusher_request *req) {
free(req->file);
}

bool
sc_file_pusher_init(struct sc_file_pusher *fp, const char *serial,
const char *push_target) {
const char *push_target,
struct sc_controller *controller) {
assert(serial);
assert(controller);

sc_vecdeque_init(&fp->queue);

Expand Down Expand Up @@ -54,6 +86,7 @@ sc_file_pusher_init(struct sc_file_pusher *fp, const char *serial,
fp->stopped = false;

fp->push_target = push_target ? push_target : DEFAULT_PUSH_TARGET;
fp->controller = controller;

return true;
}
Expand Down Expand Up @@ -116,9 +149,6 @@ run_file_pusher(void *data) {
const char *serial = fp->serial;
assert(serial);

const char *push_target = fp->push_target;
assert(push_target);

for (;;) {
sc_mutex_lock(&fp->mutex);
while (!fp->stopped && sc_vecdeque_is_empty(&fp->queue)) {
Expand All @@ -144,11 +174,22 @@ run_file_pusher(void *data) {
}
} else {
LOGI("Pushing %s...", req.file);
bool ok = sc_adb_push(intr, serial, req.file, push_target, 0);
bool ok = sc_adb_push(intr, serial, req.file, fp->push_target, 0);
if (ok) {
LOGI("%s successfully pushed to %s", req.file, push_target);
LOGI("%s successfully pushed to %s", req.file, fp->push_target);
char *remote = build_remote_path(fp->push_target, req.file);
if (remote) {
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_SCAN_FILE;
msg.scan_file.path = remote; // ownership transferred
if (!sc_controller_push_msg(fp->controller, &msg)) {
LOGW("Could not request MediaStore scan for %s",
remote);
free(remote);
}
}
} else {
LOGE("Failed to push %s to %s", req.file, push_target);
LOGE("Failed to push %s to %s", req.file, fp->push_target);
}
}

Expand Down
6 changes: 5 additions & 1 deletion app/src/file_pusher.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
#include "util/thread.h"
#include "util/vecdeque.h"

struct sc_controller;

enum sc_file_pusher_action {
SC_FILE_PUSHER_ACTION_INSTALL_APK,
SC_FILE_PUSHER_ACTION_PUSH_FILE,
Expand All @@ -24,6 +26,7 @@ struct sc_file_pusher_request_queue SC_VECDEQUE(struct sc_file_pusher_request);
struct sc_file_pusher {
char *serial;
const char *push_target;
struct sc_controller *controller; // used to send SCAN_FILE requests
sc_thread thread;
sc_mutex mutex;
sc_cond event_cond;
Expand All @@ -36,7 +39,8 @@ struct sc_file_pusher {

bool
sc_file_pusher_init(struct sc_file_pusher *fp, const char *serial,
const char *push_target);
const char *push_target,
struct sc_controller *controller);

void
sc_file_pusher_destroy(struct sc_file_pusher *fp);
Expand Down
3 changes: 2 additions & 1 deletion app/src/scrcpy.c
Original file line number Diff line number Diff line change
Expand Up @@ -497,7 +497,8 @@ scrcpy(struct scrcpy_options *options) {

if (options->window && options->control) {
if (!sc_file_pusher_init(&s->file_pusher, serial,
options->push_target)) {
options->push_target,
&s->controller)) {
goto end;
}
fp = &s->file_pusher;
Expand Down
6 changes: 6 additions & 0 deletions doc/control.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,3 +138,9 @@ The target directory can be changed on start:
```bash
scrcpy --push-target=/sdcard/Movies/
```

After each successful push, _scrcpy_ asks `MediaStore` to scan the pushed file
so that it appears in media apps. Note that some gallery apps only show files
from a fixed list of folders (typically `DCIM/Camera`) in their main view; the
file is then still reachable through the system Photo Picker and the folder
view, but may not appear on the gallery home screen.
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public final class ControlMessage {
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;

Expand Down Expand Up @@ -187,6 +188,13 @@ public static ControlMessage createResizeDisplay(int width, int 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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ public ControlMessage read() throws IOException {
return parseCameraSetTorch();
case ControlMessage.TYPE_RESIZE_DISPLAY:
return parseResizeDisplay();
case ControlMessage.TYPE_SCAN_FILE:
return parseScanFile();
default:
throw new ControlProtocolException("Unknown event type: " + type);
}
Expand Down Expand Up @@ -183,6 +185,11 @@ private ControlMessage parseResizeDisplay() throws IOException {
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();
Expand Down
23 changes: 23 additions & 0 deletions server/src/main/java/com/genymobile/scrcpy/control/Controller.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.genymobile.scrcpy.AndroidVersions;
import com.genymobile.scrcpy.AsyncProcessor;
import com.genymobile.scrcpy.CleanUp;
import com.genymobile.scrcpy.FakeContext;
import com.genymobile.scrcpy.Options;
import com.genymobile.scrcpy.device.Device;
import com.genymobile.scrcpy.display.DisplayInfo;
Expand All @@ -23,7 +24,10 @@
import com.genymobile.scrcpy.wrappers.ServiceManager;

import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.MediaStore;
import android.os.SystemClock;
import android.util.Pair;
import android.view.InputDevice;
Expand Down Expand Up @@ -335,6 +339,9 @@ private boolean handleEvent() throws IOException {
case ControlMessage.TYPE_RESET_VIDEO:
resetVideo();
return true;
case ControlMessage.TYPE_SCAN_FILE:
scanFile(msg.getText());
return true;
default:
// fall through
}
Expand Down Expand Up @@ -875,4 +882,20 @@ private void resizeDisplay(int width, int height) {
NewDisplayCapture newDisplayCapture = (NewDisplayCapture) surfaceCapture;
newDisplayCapture.requestResize(width, height);
}

private void scanFile(String path) {
if (path == null || path.isEmpty()) {
return;
}
try {
// MediaStore.scanFile() is @hide; call the same provider method directly.
// Equivalent to: adb shell content call --uri content://media --method scan_file --arg <path>
Comment thread
rom1v marked this conversation as resolved.
Bundle out = FakeContext.get().getContentResolver()
.call(MediaStore.AUTHORITY, "scan_file", path, null);
Uri uri = (out != null) ? out.<Uri>getParcelable(Intent.EXTRA_STREAM) : null;
Ln.i("MediaStore scan " + path + " -> " + uri);
} catch (Throwable t) {
Ln.e("MediaStore scan failed for " + path, t);
}
}
}