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

Actions on start exit #3191

Open
wants to merge 5 commits into
base: dev
Choose a base branch
from
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
59 changes: 59 additions & 0 deletions app/src/cli.c
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
#define OPT_OTG 1036
#define OPT_NO_CLEANUP 1037
#define OPT_PRINT_FPS 1038
#define OPT_HOOK_SCRIPT 1039

struct sc_option {
char shortopt;
Expand Down Expand Up @@ -206,6 +207,18 @@ static const struct sc_option options[] = {
.longopt = "help",
.text = "Print this help.",
},
{
.longopt_id = OPT_HOOK_SCRIPT,
.longopt = "hook-script",
.argdesc = "path",
.text = "The path to a linux shell script which is run on your device "
"when a meaningful event happens.\n"
"The script is run with multiple parameters. The ones actually "
"used will depend on the event and intended functionality.\n"
"In this version, only the events ('START', 'STOP') have been "
"implemented. Others may be implemented in the future.\n"
"For details on the parameters, check the README manual",
},
{
.longopt_id = OPT_LEGACY_PASTE,
.longopt = "legacy-paste",
Expand Down Expand Up @@ -1326,6 +1339,47 @@ sc_parse_shortcut_mods(const char *s, struct sc_shortcut_mods *mods) {
}
#endif


static bool
parse_hook_script(const char *hook_script_path, char **hook_script) {
const int MAX_ACCEPTABLE_SIZE = 1048576; // 1MB
if(!hook_script_path) {
return false;
}

FILE *script_file = fopen(hook_script_path, "rb");
if(script_file == NULL){
perror("Cannot open script file\n");
return false;
}
fseek(script_file, 0, SEEK_END);
long ssize = ftell(script_file);
fseek(script_file, 0, SEEK_SET);

if(ssize > MAX_ACCEPTABLE_SIZE){
LOGE("Script file too large. "
"Only up to 1MB (%d bytes) is accepted\n", MAX_ACCEPTABLE_SIZE);
return false;
}


*hook_script = malloc(ssize + 1);
if(*hook_script == NULL){
LOG_OOM();
return false;
}
if(!fread(*hook_script, ssize, 1, script_file)){
perror("Cannot read script file");
return false;
}
fclose(script_file);

(*hook_script)[ssize] = '\0';


return true;
}

static bool
parse_record_format(const char *optarg, enum sc_record_format *format) {
if (!strcmp(optarg, "mp4")) {
Expand Down Expand Up @@ -1574,6 +1628,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
case OPT_FORWARD_ALL_CLICKS:
opts->forward_all_clicks = true;
break;
case OPT_HOOK_SCRIPT:
if (!parse_hook_script(optarg, &opts->hook_script)) {
return false;
}
break;
case OPT_LEGACY_PASTE:
opts->legacy_paste = true;
break;
Expand Down
1 change: 1 addition & 0 deletions app/src/options.h
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ struct scrcpy_options {
bool disable_screensaver;
bool forward_key_repeat;
bool forward_all_clicks;
char * hook_script;
bool legacy_paste;
bool power_off_on_close;
bool clipboard_autosync;
Expand Down
1 change: 1 addition & 0 deletions app/src/scrcpy.c
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@ scrcpy(struct scrcpy_options *options) {
.codec_options = options->codec_options,
.encoder_name = options->encoder_name,
.force_adb_forward = options->force_adb_forward,
.hook_script = options->hook_script,
.power_off_on_close = options->power_off_on_close,
.clipboard_autosync = options->clipboard_autosync,
.downsize_on_error = options->downsize_on_error,
Expand Down
18 changes: 18 additions & 0 deletions app/src/server.c
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ sc_server_params_copy(struct sc_server_params *dst,
COPY(crop);
COPY(codec_options);
COPY(encoder_name);
COPY(hook_script);
COPY(tcpip_dst);
#undef COPY

Expand Down Expand Up @@ -233,6 +234,23 @@ execute_server(struct sc_server *server,
if (params->encoder_name) {
ADD_PARAM("encoder_name=%s", params->encoder_name);
}
if (params->hook_script) {
char* requoted_hook;
int64_t replace_result = sc_str_find_replace(params->hook_script, "'", "'\"'\"'", &requoted_hook);
Copy link
Contributor Author

@brunoais brunoais Apr 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unlike what would be expected, adb shell just concatenates the arguments as strings same
as if they were just separated by spaces. Sending the commands in one argument
or sending as multiple arguments doesn't preduce any different outcome.
Sad but that's how adb works.

try typing this in your local shell (you may need the -s argument for adb):
adb shell 'am broadcast'
vs
adb shell am broadcast

If they both work, means there's no reasonable way to deliver the arguments as-is, even when using execvp (which scrcpy is using).
Sadly, they have to be escaped.

Whether the escape be done here or before the calling stack to call adb shell is up for debate. However, no doubt it's needed and no doubt the behavior adb has is improper when intending to provide arguments as-is

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unlike what would be expected, adb shell just concatenates the arguments as strings same
as if they were just separated by spaces

In fact, it depends on the adb version and the platform. It's a total mess. You can't rely on any specific behavior here. Adding quotes might break on some platforms and some adb versions.

Anyway, in practice, the command line should not exceed 256 chars (it crashes on some devices), so the script content could not be passed as a server argument.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In that case, should I add sending the script to control_msg.c by queueing a message using sc_controller_push_msg() sometime in the beginning of the program execution while also implementing a new type at sc_control_msg_serialize() for that new feature (and then adding the counter-part code in the server)?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know what to do. I have mixed feelings about this feature.

should I add sending the script to control_msg.c by queueing a message using sc_controller_push_msg()

This would require to start the CleanUp process only after this message is received :/

Copy link
Contributor Author

@brunoais brunoais Apr 25, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know what to do. I have mixed feelings about this feature.

What about sending much of the first data through stdin? It can be encoded in some way and then just sent to stdin when adb shell is run.
With that, we can also deal with the issue of limited size of args, the argv inconsistency, etc... and just send all arguments through a stream in stdin as soon as the stdin stream exists. Maybe just encoding as base64 is enough and then ending with maybe the '\0'

This would require to start the CleanUp process only after this message is received :/

Only if a script exists but yes, if a script was set, it would need to wait.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rom1v Any answers you can give before I try again? It's been a few weeks

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rom1v May I proceed to try to implement what I proposed? So far I can tell it appears to work.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have mixed feelings about this. IMO this feature is secondary, it adds complexity and requires to change how the parameters are passed, and apparently it does not behave the same way on all adb/devices. So TBH I'm not very keen it on for the moment :/

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright. In that case this and the alternative way that was already shot down are shot down.
I'll see if I can find an alternative way to process this.

Maybe it can be sent as a ControlMessage message like most features are done. Possibly in a similar way that the clipboard message is sent.
However, it would be convenient to increase the limit to 1«19 512KB. That way a script can be ~400KB which would allow a lot of flexibility.

I think that the downside of using the ControlMessage API is acceptable with that upside of stability between versions.

What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rom1v ?

switch(replace_result){
case -2:
LOG_OOM();
break;
case -1:
case 0:
ADD_PARAM("hook_script='%s'", params->hook_script);
break;
default:
ADD_PARAM("hook_script='%s'", requoted_hook);
free(requoted_hook);
}

}
if (params->power_off_on_close) {
ADD_PARAM("power_off_on_close=true");
}
Expand Down
1 change: 1 addition & 0 deletions app/src/server.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ struct sc_server_params {
bool show_touches;
bool stay_awake;
bool force_adb_forward;
const char * hook_script;
bool power_off_on_close;
bool clipboard_autosync;
bool downsize_on_error;
Expand Down
70 changes: 70 additions & 0 deletions app/src/util/str.c
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,76 @@ sc_str_join(char *dst, const char *const tokens[], char sep, size_t n) {
return n;
}

int64_t
sc_str_find_replace(char* input, char* find, char* replace, char** output) {

uint64_t inputLen;
int64_t findLen = strlen(find);
int64_t replaceLen = strlen(replace);

if(findLen < 1){
return 0;
}


uint64_t first_match = -1;
uint32_t matchCount = 0;

for(inputLen = 0; input[inputLen] != '\0'; inputLen++){
// strncmp and not memcmp because it needs to detect null terminating string. Otherwise, it may overrrun.
if(input[inputLen] == find[0] && strncmp(&(input[inputLen]), find, findLen) == 0){
if(first_match == (uint64_t) -1){
first_match = inputLen;
}
matchCount++;
}
}
printf("matches: %u; \n", matchCount);
inputLen++;
if(matchCount == 0){
return 0;
}

int64_t output_size = inputLen - ( matchCount * (findLen - replaceLen));

if(output_size <= 0){
return -2;
}

// Caller is responsible for freeing this
char* output_str = malloc(output_size);

if(output_str == NULL){
printf("inputi: %p\n", output_str);
return -2;
}
*output = output_str;

memcpy(output_str, input, first_match);

uint64_t inputi = first_match;
uint64_t desti = first_match;

while (input[inputi] != '\0') {
printf("inputi: %lu; %i; %lu; %lu; %s\n", inputi, input[inputi] == find[0], output_size, inputLen, &(input[inputi]));
if(input[inputi] == find[0] && strncmp(&(input[inputi]), find, findLen) == 0){
memcpy(&(output_str[desti]), replace, replaceLen);
// printf("replaced: %s\n", output_str);
desti += replaceLen;
inputi += findLen;
} else {
// printf("outputstr: %s\n", output_str);
output_str[desti++] = input[inputi++];
}
}

output_str[output_size - 1] = '\0';

return output_size;

}


char *
sc_str_quote(const char *src) {
size_t len = strlen(src);
Expand Down
21 changes: 21 additions & 0 deletions app/src/util/str.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,27 @@ sc_strncpy(char *dest, const char *src, size_t n);
size_t
sc_str_join(char *dst, const char *const tokens[], char sep, size_t n);

/**
* String find and replace all occurrences.
* Compatible with different sizes of find and the replacement
* Only creates output string if changes exist
* WARNING: You are responsible for freeing the output if it's not NULL
*
* @param input The string to find on and replace with matches found
* @param find What to find in the input
* @param replace What to replace with for each found instance from find
* @param output Null or a pointer to the char* which contains the replaced char*
* @return int64_t
* if > 0: The size of the string in output
* if < 1: output should be ignored and:
* if == 0: Nothing changed from the original string
* if == -2: Overflow when trying to create the replaced string or OOM
*
*
*/
int64_t
sc_str_find_replace(char* input, char* find, char* replace, char** output);

/**
* Quote a string
*
Expand Down
25 changes: 21 additions & 4 deletions server/src/main/java/com/genymobile/scrcpy/CleanUp.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ 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 int displayId;

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

public Config() {
// Default constructor, the fields are initialized by CleanUp.configure()
Expand All @@ -58,6 +59,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;
hookScript = in.readString();
}

@Override
Expand All @@ -75,6 +77,7 @@ public void writeToParcel(Parcel dest, int flags) {
options |= FLAG_POWER_OFF_SCREEN;
}
dest.writeByte(options);
dest.writeString(hookScript);
}

private boolean hasWork() {
Expand Down Expand Up @@ -116,14 +119,18 @@ 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,
String hookScript
)
throws IOException {
Config config = new Config();
config.displayId = displayId;
config.disableShowTouches = disableShowTouches;
config.restoreStayOn = restoreStayOn;
config.restoreNormalPowerMode = restoreNormalPowerMode;
config.powerOffScreen = powerOffScreen;
config.hookScript = hookScript == null ? "" : hookScript;

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

if (!config.hookScript.isEmpty()) {
try {
Command.execShellScript(config.hookScript, "stop");
} catch (IOException e) {
Ln.e("Something failed while trying to run the stop hook", e);
} catch (InterruptedException e) {
Ln.e("Got interrupted while running the start hook", e);
}
}
}
}
42 changes: 41 additions & 1 deletion server/src/main/java/com/genymobile/scrcpy/Command.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.genymobile.scrcpy;

import java.io.IOException;
import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Scanner;

public final class Command {
Expand All @@ -17,6 +19,44 @@ public static void exec(String... cmd) throws IOException, InterruptedException
}
}

public static void execShellScript(String script, String... args) throws IOException, InterruptedException {

ArrayList<String> cmd = new ArrayList<>();
cmd.add("sh");
cmd.add("-s");
cmd.addAll(Arrays.asList(args));

Process process = Runtime.getRuntime().exec(cmd.toArray(new String[]{}));
BufferedReader err = new BufferedReader(new InputStreamReader(process.getErrorStream()));
BufferedReader output = new BufferedReader(new InputStreamReader(process.getInputStream()));
BufferedWriter input = new BufferedWriter(new OutputStreamWriter(process.getOutputStream()));
input.write(script);
input.close();

StringBuilder fullLines = new StringBuilder();
String line;
if (Ln.isEnabled(Ln.Level.DEBUG)) {
while ((line = output.readLine()) != null) {
fullLines.append(line);
fullLines.append("\n");
}
Ln.d("Custom script output:\n---\n" + fullLines + "\n----\n");
}
fullLines = new StringBuilder();
if (Ln.isEnabled(Ln.Level.WARN)) {
while ((line = err.readLine()) != null) {
fullLines.append(line);
fullLines.append("\n");
}
Ln.w("Custom script err:\n---\n" + fullLines + "\n----\n");
}

int exitCode = process.waitFor();
if (exitCode != 0) {
throw new IOException("Custom script with args: " + Arrays.toString(args) + " returned with value " + exitCode);
}
}

public static String execReadLine(String... cmd) throws IOException, InterruptedException {
String result = null;
Process process = Runtime.getRuntime().exec(cmd);
Expand Down
9 changes: 9 additions & 0 deletions server/src/main/java/com/genymobile/scrcpy/Options.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public class Options {
private boolean stayAwake;
private List<CodecOption> codecOptions;
private String encoderName;
private String hookScript;
private boolean powerOffScreenOnClose;
private boolean clipboardAutosync = true;
private boolean downsizeOnError = true;
Expand Down Expand Up @@ -132,6 +133,14 @@ public void setEncoderName(String encoderName) {
this.encoderName = encoderName;
}

public void setHookScript(String hookScript) {
this.hookScript = hookScript;
}

public String getHookScript() {
return this.hookScript;
}

public void setPowerOffScreenOnClose(boolean powerOffScreenOnClose) {
this.powerOffScreenOnClose = powerOffScreenOnClose;
}
Expand Down
Loading