-
-
Notifications
You must be signed in to change notification settings - Fork 11k
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
[Feature] Drag and drop to install apk files from your computer #133
Conversation
Oh, I saw the title of the PR in my mails, and thought it was a feature request, I didn't realize it was a pull request 😄 That's cool, I didn't know about However, currently, the installation blocks the main thread (so the UI is frozen), it should be executed in a separate thread instead. (Besides, since it is a new feature, it should target |
Thanks :) |
dc68d99 commit is a quick fix. Now, the UI should not be frozen. |
That's true. However, each "adb install" child process will not be reaped, and will stay zombies until the scrcpy process terminates. Instead, I suggest to create a thread, keep a reference to it so that you can join it when scrcpy terminates. A priori, it should be similar to what By the way, all this APK installation stuff (what you currently implement in What do you think? ¹These implementation details include decisions about:
|
My idea was to make an executor thread which takes commands from a command queue and execute them (thus serialized on the same thread).
It depends on adb, I think we can expect the same behaviour as when we disconnect the phone while adb is installing something on it. |
👍 The executor thread could be created only on the first time it is required (APK installations will probably be rare compared to scrcpy executions).
Yes, killing the process (like |
The latest commit should add this drag&drop feature in a more proper way. If someday someone needs to execute other commands apart from "adb install" , it would be nice to have a command queue instead of a filename queue to be more generic. |
Thank you 👍 I will review this when I have some time, hopefully this week-end. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good work, it works well without freezing the UI 👍
You reused the structure of existing code, so this makes the new code consistent, that's great. 😄
I have two main requests before merging it:
- the apk queue should not reserve space for
MAX_FILENAME_SIZE
(that's wasteful and unsafe if the filename is more thanMAX_FILENAME_SIZE
). Instead it should just store pointers to allocated strings (and free them as necessary) IMO. - the lazy initialization should be internal to
apkinstaller.h
. IMO the caller should always initially callinstaller_init()
as if the installer queue/thread were not lazy initialized (it's an implementation detail, we should be able to change the lazy-initialization behavior without the caller being aware of it). In that case,installer_init()
would just initialize the fiels, but not create the thread, etc. But if you want, after all, I think it's ok not to implement lazy initialization (this is probably a premature optimization that I suggested).
In addition, some suggestions:
- pass
-r
argument toadb install
to support upgrades:adb install -r file.apk
- use the same name for the file and the struct (either
installer.h
/struct installer
, eitherapkinstaller.h
/struct apk_installer
)
I also commented directly on the diff for tiny details.
app/src/apkinstaller.c
Outdated
} | ||
|
||
void apk_queue_destroy(struct apk_queue *queue) { | ||
// do nothing as we are on the stack |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there is nothing to do, but we are not on the stack 😉
app/src/apkinstaller.c
Outdated
SDL_DestroyMutex(installer->mutex); | ||
apk_queue_destroy(&installer->queue); | ||
SDL_free((void *) installer->serial); | ||
installer->initialized = SDL_FALSE; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After destroy
, the installer is considered in an undefined state, there is no need to set its variables.
app/src/apkinstaller.h
Outdated
void installer_join(struct installer *installer); | ||
|
||
// install an apk | ||
SDL_bool installer_push_apk(struct installer *installer, const char* filename); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The name is confusing, we push a request for apk installation into the queue, we don't "push" an apk (it may be confused with adb push
).
app/src/command.c
Outdated
@@ -73,6 +73,11 @@ process_t adb_push(const char *serial, const char *local, const char *remote) { | |||
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); | |||
} | |||
|
|||
process_t adb_install(const char *serial, const char *local) { | |||
const char *const adb_cmd[] = {"install", local}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
{"install", "-r", local}
to support upgrades 😉
app/src/scrcpy.c
Outdated
@@ -102,6 +104,18 @@ static void event_loop(void) { | |||
case SDL_MOUSEBUTTONUP: | |||
input_manager_process_mouse_button(&input_manager, &event.button); | |||
break; | |||
case SDL_DROPFILE: | |||
|
|||
if (!installer.initialized) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO, the fact that the installer is "lazy initialized" should not leak to the caller (it's an implementation detail).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool! I was wondering whether or not it was an implementation detail.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, it is possible not to hide the lazy initialization inside the installer: the installer could always be fully initialized on init, but the caller may call init only when necessary. In that case, the initialized flag would be on the caller side.
app/src/apkinstaller.h
Outdated
#define MAX_FILENAME_SIZE 1024 | ||
|
||
|
||
#include "apkinstaller.h" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
😕
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry about that 👎
app/src/apkinstaller.h
Outdated
#define APK_INSTALLER_H | ||
|
||
#define APK_QUEUE_SIZE 16 | ||
#define MAX_FILENAME_SIZE 1024 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should be after the includes
app/src/apkinstaller.c
Outdated
#include "lockutil.h" | ||
#include "log.h" | ||
#include "command.h" | ||
#include <string.h> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(nitpicking)
For consistency:
#include "apkinstaller.h"
#include <string.h>
#include "command.h"
#include "lockutil.h"
#include "log.h"
(first the associated .h, then the sorted <>
includes, then the sorted ""
includes)
app/src/apkinstaller.h
Outdated
.current_process = PROCESS_NONE \ | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
double empty line
app/src/apkinstaller.c
Outdated
#include "command.h" | ||
#include <string.h> | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
double newline
Big thanks for your review and the time spent, I will work on it 👍 |
Thanks for the advice, it should be better now. |
Thank you for you changes. I will probably not have time to review this week-end, but don't worry, I'm thinking about it 😉 |
Hi, Thank you for the feature. I fixed some memory leaks and released the mutex while installing (I made me fix the controller for the same reason). Here are the changes I suggest: diff --git a/app/src/installer.c b/app/src/installer.c
index 95a15db..2bef19d 100644
--- a/app/src/installer.c
+++ b/app/src/installer.c
@@ -44,7 +44,8 @@ SDL_bool apk_queue_take(struct apk_queue *queue, char **apk) {
if (apk_queue_is_empty(queue)) {
return SDL_FALSE;
}
- *apk = SDL_strdup(queue->data[queue->tail]);
+ // transfer ownership
+ *apk = queue->data[queue->tail];
queue->tail = (queue->tail + 1) % APK_QUEUE_SIZE;
return SDL_TRUE;
}
@@ -64,9 +65,14 @@ SDL_bool installer_init(struct installer *installer, const char *serial) {
return SDL_FALSE;
}
- installer->serial = NULL;
if (serial) {
installer->serial = SDL_strdup(serial);
+ if (!installer->serial) {
+ LOGW("Cannot strdup serial");
+ return SDL_FALSE;
+ }
+ } else {
+ installer->serial = NULL;
}
// lazy initialization
@@ -104,35 +110,40 @@ SDL_bool installer_install_apk(struct installer *installer, const char *apk) {
return res;
}
-static SDL_bool process_install(struct installer *installer, const char *filename) {
- LOGI("%s will be installed",filename);
- process_t process = adb_install(installer->serial, filename);
- installer->current_process = process;
- return process_check_success(process, "adb install");
-}
-
static int run_installer(void *data) {
struct installer *installer = data;
- char *current_apk;
- mutex_lock(installer->mutex);
for (;;) {
+ mutex_lock(installer->mutex);
while (!installer->stopped && apk_queue_is_empty(&installer->queue)) {
cond_wait(installer->event_cond, installer->mutex);
}
if (installer->stopped) {
// stop immediately, do not process further events
+ mutex_unlock(installer->mutex);
break;
}
- while (apk_queue_take(&installer->queue, ¤t_apk)) {
- SDL_bool ok = process_install(installer,current_apk);
- if (!ok) {
- LOGD("Error during installation");
- }
+ char *current_apk;
+#ifdef BUILD_DEBUG
+ bool non_empty = apk_queue_take(&installer->queue, ¤t_apk);
+ SDL_assert(non_empty);
+#else
+ apk_queue_take(&installer->queue, ¤t_apk);
+#endif
+
+ LOGI("Installing %s...", current_apk);
+ process_t process = adb_install(installer->serial, current_apk);
+ installer->current_process = process;
+
+ mutex_unlock(installer->mutex);
+
+ if (process_check_success(process, "adb install")) {
+ LOGI("%s installed successfully", current_apk);
+ } else {
+ LOGE("Failed to install %s", current_apk);
}
+ SDL_free(current_apk);
}
- mutex_unlock(installer->mutex);
- SDL_free(current_apk);
return 0;
}
@@ -152,7 +163,7 @@ void installer_stop(struct installer *installer) {
mutex_lock(installer->mutex);
installer->stopped = SDL_TRUE;
cond_signal(installer->event_cond);
- if (installer->current_process == PROCESS_NONE) {
+ if (installer->current_process != PROCESS_NONE) {
if (!cmd_terminate(installer->current_process)) {
LOGW("Cannot terminate install process");
}
diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c
index 03bdc19..4e74bb9 100644
--- a/app/src/scrcpy.c
+++ b/app/src/scrcpy.c
@@ -152,10 +152,10 @@ SDL_bool scrcpy(const char *serial, Uint16 local_port, Uint16 max_size, Uint32 b
goto finally_destroy_server;
}
- if (!installer_init(&installer,server.serial)) {
+ if (!installer_init(&installer, server.serial)) {
ret = SDL_FALSE;
server_stop(&server);
- goto finally_destroy_installer;
+ goto finally_destroy_frames;
}
decoder_init(&decoder, &frames, device_socket);
@@ -165,7 +165,7 @@ SDL_bool scrcpy(const char *serial, Uint16 local_port, Uint16 max_size, Uint32 b
if (!decoder_start(&decoder)) {
ret = SDL_FALSE;
server_stop(&server);
- goto finally_destroy_frames;
+ goto finally_destroy_installer;
}
if (!controller_init(&controller, device_socket)) { Please review 😉 If it's ok, I'll squash all these commits into one on I'll also add a workaround for Windows to support spaces in path: diff --git a/app/src/command.c b/app/src/command.c
index 463eb42..6a7c1b9 100644
--- a/app/src/command.c
+++ b/app/src/command.c
@@ -74,6 +74,16 @@ process_t adb_push(const char *serial, const char *local, const char *remote) {
}
process_t adb_install(const char *serial, const char *local) {
+#ifdef __WINDOWS__
+ // Windows will parse the string, so the local name must be quoted (see sys/win/command.c)
+ size_t len = strlen(local);
+ char quoted[len + 3];
+ memcpy("ed[1], local, len);
+ quoted[0] = '"';
+ quoted[len + 1] = '"';
+ quoted[len + 2] = '\0';
+ local = quoted;
+#endif
const char *const adb_cmd[] = {"install", "-r", local};
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
} |
Oh, I was not aware of the spaces problem for Windows path, that's cool to know (I don't really use Windows a lot). Something in the mutex code was interpelling me too but I couldn't find why, nice job 💯 |
Commit e2a2973, and released in v1.2 👍 🎉 |
I have just added a little feature to install apk files by using drag and drop.
So, just drag and drop an apk file and wait for its installation to complete.