Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Server running socket #2407

Open
wants to merge 11 commits into
base: dev
Choose a base branch
from
83 changes: 83 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,89 @@ The target directory can be changed on start:
scrcpy --push-target=/sdcard/Download/
```

### Android features

#### Announce scrcpy state of execution

**(Advanced feature)**

Turn on the announcement of scrcpy current status.
Those announcements are done using the [broadcast intents] feature of Android.
If no value is provided with this argument, all intents are turned on.

[broadcast intents]: https://developer.android.com/reference/android/content/Intent

Currently, the only events that exist are:

| Option | Description | [Intent Action] | [Intent Extras]
| ----------|:---------------------------------------------- |:---------------------------------|:---------------
| `start` | scrcpy starts | `com.genymobile.scrcpy.START` | STARTUP: true
| `socket` | a socket for the duration of scrcpy's run created | `com.genymobile.scrcpy.SOCKET` | SOCKET: int
| `stop` | scrcpy stops (best effort) | `com.genymobile.scrcpy.STOP` | SHUTDOWN: true
| `cleaned` | scrcpy has finished cleaning up (best effort) | `com.genymobile.scrcpy.CLEANED` | SHUTDOWN: true

[Intent Action]: https://developer.android.com/reference/android/content/Intent#setAction(java.lang.String)
[Intent Extras]: https://developer.android.com/reference/android/content/Intent#putExtra(java.lang.String,%20android.os.Parcelable)


**Important:**
1. `stop` and `cleaned` **may not happen** in specific cases. For example,
if debugging is turned off, scrcpy process is immediately killed without a chance to cleanup.
2. The only guaranteed way to know if scrcpy has exited is by listening for a connection reset on
the socket
3. This option is intended **for advanced users**. By using this
feature, all apps on your phone will know scrcpy has connected
Unless that is what you want, and you know what that means
do not use this feature
4. In order for this argument to produce visible results you must create
some automation to listen to android broadcast intents.
Such as with your own app or with automation apps such as [Tasker].


Following [Android intent rules], all intents fields/keys prefixed with:
`com.genymobile.scrcpy.`
In case of Actions, it is followed by the intent name in caps. For example,
the 'start' intent has the action:
`com.genymobile.scrcpy.START`


[Android intent rules]: https://developer.android.com/reference/android/content/Intent#setAction(java.lang.String)

Additionally, there are two boolean fields (that may not be present) in the extra data section of the intents:

1. `com.genymobile.scrcpy.STARTUP` if present and `true`, scrcpy is starting up.
2. `com.genymobile.scrcpy.SHUTDOWN` if present and `true`, scrcpy is shutting down.
3. `com.genymobile.scrcpy.SOCKET` if present and an int, scrcpy has created a socket on the specified
port and you can listen to it to confirm when scrcpy exits

More extra fields will be present in the future.

In case you listen to the socket provided by `com.genymobile.scrcpy.SOCKET`, note that **no information will
be exchanged through it**. Even though bytes will be transmitted through it, they are only a test to
ensure the connection is still alive and have no meaning.
A connection reset followed by connection refused when trying to reestablish the connection is the
only infallible way to ensure that scrcpy has turned off.


For convinience with automation tools such as [Tasker], scrcpy also writes to the data field of the intents.
The scheme is `scrcpy-status`.

[Tasker]: https://tasker.joaoapps.com/

**Example usages:**

```bash
scrcpy --broadcast-intents
```

```bash
scrcpy --broadcast-intents=start
```

```bash
scrcpy --broadcast-intents start,cleaned
```


### Audio forwarding

Expand Down
95 changes: 95 additions & 0 deletions app/src/cli.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,45 @@ scrcpy_print_usage(const char *arg0) {
" Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n"
" Default is " STR(DEFAULT_BIT_RATE) ".\n"
"\n"
" --broadcast-intents [value[, ...]]\n"
" (Advanced feature)\n"
" Turn on the broadcast of intents with the status of scrcpy \n"
" options are: start, socket, stop, cleaned\n"
" Each of these will arm the corresponding intent\n"
" start: announce finished setting up\n"
" socket: announce isAlive server port\n"
" stop: announce shut down started (best effort)\n"
" cleaned: announce cleanup finished (best effort)\n"
" \n"
" If you ommit the value, all intents are turned on\n"
" \n"
" All intents have the action and extra fields prefixed with: \n"
" com.genymobile.scrcpy.\n"
" Which is then followed by the intent name in caps. For example,\n"
" the 'start' intent has the action:\n"
" com.genymobile.scrcpy.START\n"
"\n"
" There are two boolean extras use to ease\n"
" the parsing process of the intents:\n"
" 1. com.genymobile.scrcpy.STARTUP if present and true,\n"
" scrcpy is starting up.\n"
" 2. com.genymobile.scrcpy.SHUTDOWN if present and true,\n"
" scrcpy is shutting down.\n"
"\n"
" socket has a different extra\n"
" com.genymobile.scrcpy.SOCKET which is the port where the socket\n"
" listens to.\n"
" Listening for a connection reset on the socket is the only\n"
" guaranteed way to know when scrcpy disconnects or crashes\n"
" \n"
" Notes:\n"
" 1. stop and cleaned may not happen in specific cases. For example, \n"
" if debugging is turned off, scrcpy process is immediately killed \n"
" 2. This option is intended for advanced users. By using this \n"
" feature, all apps on your phone will know scrcpy has connected\n"
" Unless that is what you want, and you know what that means\n"
" do not use this feature\n"
"\n"
" --codec-options key[:type]=value[,...]\n"
" Set a list of comma-separated key:type=value options for the\n"
" device encoder.\n"
Expand Down Expand Up @@ -656,6 +695,53 @@ guess_record_format(const char *filename) {
return 0;
}


static bool
parse_intent_broadcast(const char *s, uint32_t *intents) {

// if no arg provided activates all intents for all intents and purposes
if(!s){
*intents = -1;
return true;
}

for (;;) {
char *comma = strchr(s, ',');

assert(!comma || comma > s);
size_t limit = comma ? (size_t) (comma - s) : strlen(s);


#define STREQ(literal, s, len) \
((sizeof(literal)-1 == len) && !memcmp(literal, s, len))

if (STREQ("start", s, limit)) {
*intents |= SC_INTENT_BROADCAST_START;
} else if (STREQ("socket", s, limit)) {
*intents |= SC_INTENT_BROADCAST_SOCKET;
} else if (STREQ("stop", s, limit)) {
*intents |= SC_INTENT_BROADCAST_STOP;
} else if (STREQ("cleaned", s, limit)) {
*intents |= SC_INTENT_BROADCAST_CLEANED;
} else {
LOGE("Unknown broadcast intent: %.*s "
"(must be one of: start, socket, stop, cleaned)",
(int) limit, s);
return false;
}
#undef STREQ

if (!comma) {
break;
}

s = comma + 1;
}

return true;
}


#define OPT_RENDER_EXPIRED_FRAMES 1000
#define OPT_WINDOW_TITLE 1001
#define OPT_PUSH_TARGET 1002
Expand Down Expand Up @@ -684,6 +770,7 @@ guess_record_format(const char *filename) {
#define OPT_ENCODER_NAME 1025
#define OPT_POWER_OFF_ON_CLOSE 1026
#define OPT_V4L2_SINK 1027
#define OPT_INTENT_BROADCAST 1028

bool
scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
Expand Down Expand Up @@ -739,6 +826,8 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
OPT_WINDOW_BORDERLESS},
{"power-off-on-close", no_argument, NULL,
OPT_POWER_OFF_ON_CLOSE},
{"intent-broadcast", optional_argument, NULL,
OPT_INTENT_BROADCAST},
{NULL, 0, NULL, 0 },
};

Expand Down Expand Up @@ -917,6 +1006,12 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
opts->v4l2_device = optarg;
break;
#endif

case OPT_INTENT_BROADCAST:
if (!parse_intent_broadcast(optarg, &opts->intent_broadcasts)) {
return false;
}
break;
default:
// getopt prints the error message on stderr
return false;
Expand Down
1 change: 1 addition & 0 deletions app/src/scrcpy.c
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ scrcpy(const struct scrcpy_options *options) {
.encoder_name = options->encoder_name,
.force_adb_forward = options->force_adb_forward,
.power_off_on_close = options->power_off_on_close,
.intent_broadcasts = options->intent_broadcasts,
};
if (!server_start(&s->server, &params)) {
goto end;
Expand Down
11 changes: 11 additions & 0 deletions app/src/scrcpy.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,15 @@ struct sc_port_range {

#define SC_WINDOW_POSITION_UNDEFINED (-0x8000)


enum sc_intent_broadcast {
SC_INTENT_BROADCAST_START = 1 << 0,
SC_INTENT_BROADCAST_SOCKET = 1 << 1,
SC_INTENT_BROADCAST_STOP = 1 << 30,
SC_INTENT_BROADCAST_CLEANED = 1 << 31,
};


struct scrcpy_options {
const char *serial;
const char *crop;
Expand Down Expand Up @@ -93,6 +102,7 @@ struct scrcpy_options {
bool forward_all_clicks;
bool legacy_paste;
bool power_off_on_close;
uint32_t intent_broadcasts;
};

#define SCRCPY_OPTIONS_DEFAULT { \
Expand Down Expand Up @@ -141,6 +151,7 @@ struct scrcpy_options {
.forward_all_clicks = false, \
.legacy_paste = false, \
.power_off_on_close = false, \
.intent_broadcasts = 0, \
}

bool
Expand Down
3 changes: 3 additions & 0 deletions app/src/server.c
Original file line number Diff line number Diff line change
Expand Up @@ -256,11 +256,13 @@ execute_server(struct server *server, const struct server_params *params) {
char max_fps_string[6];
char lock_video_orientation_string[5];
char display_id_string[11];
char intent_broadcasts_string[11];
sprintf(max_size_string, "%"PRIu16, params->max_size);
sprintf(bit_rate_string, "%"PRIu32, params->bit_rate);
sprintf(max_fps_string, "%"PRIu16, params->max_fps);
sprintf(lock_video_orientation_string, "%"PRIi8, params->lock_video_orientation);
sprintf(display_id_string, "%"PRIu32, params->display_id);
sprintf(intent_broadcasts_string, "%"PRIu32, params->intent_broadcasts);
const char *const cmd[] = {
"shell",
"CLASSPATH=" DEVICE_SERVER_PATH,
Expand Down Expand Up @@ -294,6 +296,7 @@ execute_server(struct server *server, const struct server_params *params) {
params->codec_options ? params->codec_options : "-",
params->encoder_name ? params->encoder_name : "-",
params->power_off_on_close ? "true" : "false",
intent_broadcasts_string,
};
#ifdef SERVER_DEBUGGER
LOGI("Server debugger waiting for a client on device port "
Expand Down
1 change: 1 addition & 0 deletions app/src/server.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ struct server_params {
bool stay_awake;
bool force_adb_forward;
bool power_off_on_close;
uint32_t intent_broadcasts;
};

// init default values
Expand Down
34 changes: 29 additions & 5 deletions server/src/main/java/com/genymobile/scrcpy/CleanUp.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.genymobile.scrcpy;

import android.content.Intent;
import android.net.Uri;
import com.genymobile.scrcpy.wrappers.ContentProvider;
import com.genymobile.scrcpy.wrappers.ServiceManager;

Expand Down Expand Up @@ -34,9 +36,10 @@ public Config[] newArray(int size) {
}
};

private static final int FLAG_DISABLE_SHOW_TOUCHES = 1;
private static final int FLAG_RESTORE_NORMAL_POWER_MODE = 2;
private static final int FLAG_POWER_OFF_SCREEN = 4;
private static final int FLAG_DISABLE_SHOW_TOUCHES = 1 << 0;
private static final int FLAG_RESTORE_NORMAL_POWER_MODE = 1 << 1;
private static final int FLAG_POWER_OFF_SCREEN = 1 << 2;
private static final int FLAG_BROADCAST_CLEANED = 1 << 3;

private int displayId;

Expand All @@ -47,6 +50,7 @@ public Config[] newArray(int size) {
private boolean disableShowTouches;
private boolean restoreNormalPowerMode;
private boolean powerOffScreen;
private boolean broadcastCleaned;

public Config() {
// Default constructor, the fields are initialized by CleanUp.configure()
Expand All @@ -59,6 +63,7 @@ protected Config(Parcel in) {
disableShowTouches = (options & FLAG_DISABLE_SHOW_TOUCHES) != 0;
restoreNormalPowerMode = (options & FLAG_RESTORE_NORMAL_POWER_MODE) != 0;
powerOffScreen = (options & FLAG_POWER_OFF_SCREEN) != 0;
broadcastCleaned = (options & FLAG_BROADCAST_CLEANED) != 0;
}

@Override
Expand All @@ -75,11 +80,14 @@ public void writeToParcel(Parcel dest, int flags) {
if (powerOffScreen) {
options |= FLAG_POWER_OFF_SCREEN;
}
if (broadcastCleaned) {
options |= FLAG_BROADCAST_CLEANED;
}
dest.writeByte(options);
}

private boolean hasWork() {
return disableShowTouches || restoreStayOn != -1 || restoreNormalPowerMode || powerOffScreen;
return disableShowTouches || restoreStayOn != -1 || restoreNormalPowerMode || powerOffScreen || broadcastCleaned;
}

@Override
Expand Down Expand Up @@ -117,14 +125,16 @@ private CleanUp() {
// not instantiable
}

public static void configure(int displayId, int restoreStayOn, boolean disableShowTouches, boolean restoreNormalPowerMode, boolean powerOffScreen)
public static void configure(int displayId, int restoreStayOn, boolean disableShowTouches, boolean restoreNormalPowerMode,
boolean powerOffScreen, boolean broadcastCleaned)
throws IOException {
Config config = new Config();
config.displayId = displayId;
config.disableShowTouches = disableShowTouches;
config.restoreStayOn = restoreStayOn;
config.restoreNormalPowerMode = restoreNormalPowerMode;
config.powerOffScreen = powerOffScreen;
config.broadcastCleaned = broadcastCleaned;

if (config.hasWork()) {
startProcess(config);
Expand Down Expand Up @@ -187,5 +197,19 @@ public static void main(String... args) {
Device.setScreenPowerMode(Device.POWER_MODE_NORMAL);
}
}

if(config.broadcastCleaned){
Ln.i("Announce cleaned");
announceScrcpyCleaned();
}
}

private static void announceScrcpyCleaned() {

Intent cleaned = new Intent(Intents.scrcpyPrefix("CLEANED"));
cleaned.setData(Uri.parse("scrcpy-status:cleaned"));
cleaned.putExtra(Intents.scrcpyPrefix("STARTUP"), false);
cleaned.putExtra(Intents.scrcpyPrefix("SHUTDOWN"), true);
Device.sendBroadcast(cleaned);
}
}
5 changes: 5 additions & 0 deletions server/src/main/java/com/genymobile/scrcpy/Device.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.genymobile.scrcpy.wrappers.WindowManager;

import android.content.IOnPrimaryClipChangedListener;
import android.content.Intent;
import android.graphics.Rect;
import android.os.Build;
import android.os.IBinder;
Expand Down Expand Up @@ -299,4 +300,8 @@ public static void rotateDevice() {
public static ContentProvider createSettingsProvider() {
return SERVICE_MANAGER.getActivityManager().createSettingsProvider();
}

public static void sendBroadcast(Intent intent) {
SERVICE_MANAGER.getActivityManager().sendBroadcast(intent);
}
}
Loading