diff --git a/drivers/unix/os_unix.cpp b/drivers/unix/os_unix.cpp index 0497033f1d6..8c64ba10ad3 100644 --- a/drivers/unix/os_unix.cpp +++ b/drivers/unix/os_unix.cpp @@ -756,6 +756,11 @@ Dictionary OS_Unix::execute_with_pipe(const String &p_path, const List & } if (pid == 0) { + // The new process + // Create a new session-ID so parent won't wait for it. + // This ensures the process won't go zombie at the end. + setsid(); + // The child process. Vector cs; cs.push_back(p_path.utf8()); diff --git a/editor/export/editor_export_platform.h b/editor/export/editor_export_platform.h index 5200c108859..4f6ddaa480a 100644 --- a/editor/export/editor_export_platform.h +++ b/editor/export/editor_export_platform.h @@ -325,6 +325,7 @@ public: virtual bool poll_export() { return false; } virtual int get_options_count() const { return 0; } + virtual bool is_option_runnable(int p_index) const { return true; } virtual String get_options_tooltip() const { return ""; } virtual Ref get_option_icon(int p_index) const; virtual String get_option_label(int p_device) const { return ""; } diff --git a/editor/inspector/editor_property_name_processor.cpp b/editor/inspector/editor_property_name_processor.cpp index 4250bd50daf..7dcede74d7f 100644 --- a/editor/inspector/editor_property_name_processor.cpp +++ b/editor/inspector/editor_property_name_processor.cpp @@ -216,6 +216,7 @@ EditorPropertyNameProcessor::EditorPropertyNameProcessor() { capitalize_string_remaps["ik"] = "IK"; capitalize_string_remaps["image@2x"] = "Image @2x"; capitalize_string_remaps["image@3x"] = "Image @3x"; + capitalize_string_remaps["ime"] = "IME"; capitalize_string_remaps["iod"] = "IOD"; capitalize_string_remaps["ios"] = "iOS"; capitalize_string_remaps["ip"] = "IP"; @@ -269,6 +270,7 @@ EditorPropertyNameProcessor::EditorPropertyNameProcessor() { capitalize_string_remaps["rv64"] = "rv64"; capitalize_string_remaps["s3tc"] = "S3TC"; capitalize_string_remaps["scp"] = "SCP"; + capitalize_string_remaps["scrcpy"] = "scrcpy"; capitalize_string_remaps["sdf"] = "SDF"; capitalize_string_remaps["sdfgi"] = "SDFGI"; capitalize_string_remaps["sdk"] = "SDK"; diff --git a/editor/run/editor_run_native.cpp b/editor/run/editor_run_native.cpp index 11a8de2f49d..aa32cac86a9 100644 --- a/editor/run/editor_run_native.cpp +++ b/editor/run/editor_run_native.cpp @@ -71,7 +71,7 @@ void EditorRunNative::_notification(int p_what) { popup->add_icon_item(eep->get_option_icon(j), eep->get_option_label(j), 10000 * platform_idx + j); popup->set_item_tooltip(-1, eep->get_option_tooltip(j)); popup->set_item_indent(-1, 2); - if (device_shortcut_id <= 4) { + if (device_shortcut_id <= 4 && eep->is_option_runnable(j)) { // Assign shortcuts for the first 4 devices added in the list. popup->set_item_shortcut(-1, ED_GET_SHORTCUT(vformat("remote_deploy/deploy_to_device_%d", device_shortcut_id)), true); device_shortcut_id += 1; @@ -144,7 +144,9 @@ Error EditorRunNative::start_run_native(int p_id) { preset->update_value_overrides(); - emit_signal(SNAME("native_run"), preset); + if (eep->is_option_runnable(idx)) { + emit_signal(SNAME("native_run"), preset); + } BitField flags = 0; diff --git a/platform/android/export/export.cpp b/platform/android/export/export.cpp index 9b0c67a7d8a..7fe0ecb9da4 100644 --- a/platform/android/export/export.cpp +++ b/platform/android/export/export.cpp @@ -60,6 +60,19 @@ void register_android_exporter() { EDITOR_DEF_BASIC("export/android/android_sdk_path", OS::get_singleton()->has_environment("ANDROID_HOME") ? OS::get_singleton()->get_environment("ANDROID_HOME") : get_default_android_sdk_path()); EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/android_sdk_path", PROPERTY_HINT_GLOBAL_DIR)); + EDITOR_DEF_BASIC("export/android/scrcpy/path", ""); + EDITOR_DEF_BASIC("export/android/scrcpy/virtual_display", true); + EDITOR_DEF_BASIC("export/android/scrcpy/no_decorations", true); + EDITOR_DEF_BASIC("export/android/scrcpy/local_ime", true); + EDITOR_DEF_BASIC("export/android/scrcpy/screen_size", "1920x1080/120"); + EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/scrcpy/screen_size", PROPERTY_HINT_PLACEHOLDER_TEXT, "WIDTHxHEIGHT/DPI")); + +#ifdef WINDOWS_ENABLED + EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/scrcpy_path", PROPERTY_HINT_GLOBAL_FILE, "*.exe")); +#else + EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/scrcpy_path", PROPERTY_HINT_GLOBAL_FILE)); +#endif + EDITOR_DEF("export/android/force_system_user", false); EDITOR_DEF("export/android/shutdown_adb_on_exit", true); diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index 12770c5b2e6..ed88064708d 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -41,6 +41,7 @@ #include "core/version.h" #include "editor/editor_log.h" #include "editor/editor_node.h" +#include "editor/editor_string_names.h" #include "editor/export/export_template_manager.h" #include "editor/file_system/editor_paths.h" #include "editor/import/resource_importer_texture_settings.h" @@ -2265,7 +2266,16 @@ bool EditorExportPlatformAndroid::poll_export() { int EditorExportPlatformAndroid::get_options_count() const { MutexLock lock(device_lock); - return devices.size(); + return devices.size() + 1; +} + +Ref EditorExportPlatformAndroid::get_option_icon(int p_index) const { + if (p_index == 0) { + Ref theme = EditorNode::get_singleton()->get_editor_theme(); + ERR_FAIL_COND_V(theme.is_null(), Ref()); + return theme->get_icon(use_scrcpy ? SNAME("GuiChecked") : SNAME("GuiUnchecked"), EditorStringName(EditorIcons)); + } + return EditorExportPlatform::get_option_icon(p_index - 1); } String EditorExportPlatformAndroid::get_options_tooltip() const { @@ -2273,32 +2283,47 @@ String EditorExportPlatformAndroid::get_options_tooltip() const { } String EditorExportPlatformAndroid::get_option_label(int p_index) const { - ERR_FAIL_INDEX_V(p_index, devices.size(), ""); + ERR_FAIL_INDEX_V(p_index, devices.size() + 1, ""); + if (p_index == 0) { + return TTR("Mirror Android devices"); + } MutexLock lock(device_lock); - return devices[p_index].name; + return devices[p_index - 1].name; } String EditorExportPlatformAndroid::get_option_tooltip(int p_index) const { - ERR_FAIL_INDEX_V(p_index, devices.size(), ""); + ERR_FAIL_INDEX_V(p_index, devices.size() + 1, ""); + if (p_index == 0) { + return TTR("If enabled, \"scrcpy\" is used to start the project and automatically stream device display (or virtual display) content."); + } MutexLock lock(device_lock); - String s = devices[p_index].description; + String s = devices[p_index - 1].description; if (devices.size() == 1) { // Tooltip will be: // Name // Description - s = devices[p_index].name + "\n\n" + s; + s = devices[p_index - 1].name + "\n\n" + s; } return s; } String EditorExportPlatformAndroid::get_device_architecture(int p_index) const { - ERR_FAIL_INDEX_V(p_index, devices.size(), ""); + ERR_FAIL_INDEX_V(p_index, devices.size() + 1, ""); + if (p_index == 0) { + return String(); + } MutexLock lock(device_lock); - return devices[p_index].architecture; + return devices[p_index - 1].architecture; } Error EditorExportPlatformAndroid::run(const Ref &p_preset, int p_device, BitField p_debug_flags) { - ERR_FAIL_INDEX_V(p_device, devices.size(), ERR_INVALID_PARAMETER); + ERR_FAIL_INDEX_V(p_device, devices.size() + 1, ERR_INVALID_PARAMETER); + if (p_device == 0) { + use_scrcpy = !use_scrcpy; + EditorSettings::get_singleton()->set_project_metadata("android", "use_scrcpy", use_scrcpy); + devices_changed.set(); + return ERR_SKIP; + } String can_export_error; bool can_export_missing_templates; @@ -2309,7 +2334,7 @@ Error EditorExportPlatformAndroid::run(const Ref &p_preset, MutexLock lock(device_lock); - EditorProgress ep("run", vformat(TTR("Running on %s"), devices[p_device].name), 3); + EditorProgress ep("run", vformat(TTR("Running on %s"), devices[p_device - 1].name), 3); String adb = get_adb_path(); @@ -2320,7 +2345,7 @@ Error EditorExportPlatformAndroid::run(const Ref &p_preset, const bool use_wifi_for_remote_debug = EDITOR_GET("export/android/use_wifi_for_remote_debug"); const bool use_remote = p_debug_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG) || p_debug_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT); - const bool use_reverse = devices[p_device].api_level >= 21 && !use_wifi_for_remote_debug; + const bool use_reverse = devices[p_device - 1].api_level >= 21 && !use_wifi_for_remote_debug; if (use_reverse) { p_debug_flags.set_flag(DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST); @@ -2358,12 +2383,12 @@ Error EditorExportPlatformAndroid::run(const Ref &p_preset, CLEANUP_AND_RETURN(ERR_SKIP); } - print_line("Uninstalling previous version: " + devices[p_device].name); + print_line("Uninstalling previous version: " + devices[p_device - 1].name); args.push_back("-s"); - args.push_back(devices[p_device].id); + args.push_back(devices[p_device - 1].id); args.push_back("uninstall"); - if ((bool)EDITOR_GET("export/android/force_system_user") && devices[p_device].api_level >= 17) { + if ((bool)EDITOR_GET("export/android/force_system_user") && devices[p_device - 1].api_level >= 17) { args.push_back("--user"); args.push_back("0"); } @@ -2374,16 +2399,16 @@ Error EditorExportPlatformAndroid::run(const Ref &p_preset, print_verbose(output); } - print_line("Installing to device (please wait...): " + devices[p_device].name); + print_line("Installing to device (please wait...): " + devices[p_device - 1].name); if (ep.step(TTR("Installing to device, please wait..."), 2)) { CLEANUP_AND_RETURN(ERR_SKIP); } args.clear(); args.push_back("-s"); - args.push_back(devices[p_device].id); + args.push_back(devices[p_device - 1].id); args.push_back("install"); - if ((bool)EDITOR_GET("export/android/force_system_user") && devices[p_device].api_level >= 17) { + if ((bool)EDITOR_GET("export/android/force_system_user") && devices[p_device - 1].api_level >= 17) { args.push_back("--user"); args.push_back("0"); } @@ -2406,7 +2431,7 @@ Error EditorExportPlatformAndroid::run(const Ref &p_preset, args.clear(); args.push_back("-s"); - args.push_back(devices[p_device].id); + args.push_back(devices[p_device - 1].id); args.push_back("reverse"); args.push_back("--remove-all"); output.clear(); @@ -2417,7 +2442,7 @@ Error EditorExportPlatformAndroid::run(const Ref &p_preset, int dbg_port = EDITOR_GET("network/debug/remote_port"); args.clear(); args.push_back("-s"); - args.push_back(devices[p_device].id); + args.push_back(devices[p_device - 1].id); args.push_back("reverse"); args.push_back("tcp:" + itos(dbg_port)); args.push_back("tcp:" + itos(dbg_port)); @@ -2433,7 +2458,7 @@ Error EditorExportPlatformAndroid::run(const Ref &p_preset, args.clear(); args.push_back("-s"); - args.push_back(devices[p_device].id); + args.push_back(devices[p_device - 1].id); args.push_back("reverse"); args.push_back("tcp:" + itos(fs_port)); args.push_back("tcp:" + itos(fs_port)); @@ -2456,42 +2481,99 @@ Error EditorExportPlatformAndroid::run(const Ref &p_preset, if (ep.step(TTR("Running on device..."), 3)) { CLEANUP_AND_RETURN(ERR_SKIP); } - args.clear(); - args.push_back("-s"); - args.push_back(devices[p_device].id); - args.push_back("shell"); - args.push_back("am"); - args.push_back("start"); - if ((bool)EDITOR_GET("export/android/force_system_user") && devices[p_device].api_level >= 17) { - args.push_back("--user"); - args.push_back("0"); + + String scrcpy = "scrcpy"; + if (!EDITOR_GET("export/android/scrcpy/path").operator String().is_empty()) { + scrcpy = EDITOR_GET("export/android/scrcpy/path").operator String(); } - args.push_back("-a"); - args.push_back("android.intent.action.MAIN"); - // Going with implicit launch first based on the LAUNCHER category and the app's package. - args.push_back("-c"); - args.push_back("android.intent.category.LAUNCHER"); - args.push_back(get_package_name(p_preset, package_name)); + args.clear(); + if (use_scrcpy) { + args.push_back("-s"); + args.push_back(devices[p_device - 1].id); + if (EDITOR_GET("export/android/scrcpy/virtual_display").operator bool()) { + args.push_back("--new-display=" + EDITOR_GET("export/android/scrcpy/screen_size").operator String()); + if (EDITOR_GET("export/android/scrcpy/local_ime").operator bool()) { + args.push_back("--display-ime-policy=local"); + } + if (EDITOR_GET("export/android/scrcpy/no_decorations").operator bool()) { + args.push_back("--no-vd-system-decorations"); + } + } + args.push_back("--start-app=+" + get_package_name(p_preset, package_name)); - output.clear(); - err = OS::get_singleton()->execute(adb, args, &output, &rv, true); - print_verbose(output); - if (err || rv != 0 || output.contains("Error: Activity not started")) { - // The implicit launch failed, let's try an explicit launch by specifying the component name before giving up. - const String component_name = get_package_name(p_preset, package_name) + "/com.godot.game.GodotApp"; - print_line("Implicit launch failed.. Trying explicit launch using", component_name); - args.erase(get_package_name(p_preset, package_name)); - args.push_back("-n"); - args.push_back(component_name); + Dictionary data = OS::get_singleton()->execute_with_pipe(scrcpy, args, false); + if (!data.has("pid") || data["pid"].operator int() <= 0) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Run"), TTR("Could not start scrcpy executable. Configure scrcpy path in the Editor Settings (Export > Android > scrcpy > Path).")); + CLEANUP_AND_RETURN(ERR_CANT_CREATE); + } + bool connected = false; + uint64_t wait = 3000000; + uint64_t time = OS::get_singleton()->get_ticks_usec(); + output.clear(); + String err_output; + Ref fa_out = data["stdio"]; + Ref fa_err = data["stderr"]; + while (fa_out->is_open() && fa_err->is_open() && OS::get_singleton()->get_ticks_usec() - time < wait) { + PackedByteArray buf; + + buf.resize(fa_out->get_length()); + uint64_t size = fa_out->get_buffer(buf.ptrw(), buf.size()); + output.append_utf8((const char *)buf.ptr(), size); + + buf.resize(fa_err->get_length()); + size = fa_err->get_buffer(buf.ptrw(), buf.size()); + err_output.append_utf8((const char *)buf.ptr(), size); + + if (output.contains("[server] INFO: Device:")) { + connected = true; + break; + } + } + print_verbose(output); + print_verbose(err_output); + if (!connected) { + OS::get_singleton()->kill(data["pid"].operator int()); + add_message(EXPORT_MESSAGE_ERROR, TTR("Run"), TTR("Could not execute on device, scrcpy failed with the following error:\n" + err_output)); + CLEANUP_AND_RETURN(ERR_CANT_CREATE); + } + } else { + args.push_back("-s"); + args.push_back(devices[p_device - 1].id); + args.push_back("shell"); + args.push_back("am"); + args.push_back("start"); + if ((bool)EDITOR_GET("export/android/force_system_user") && devices[p_device - 1].api_level >= 17) { + args.push_back("--user"); + args.push_back("0"); + } + args.push_back("-a"); + args.push_back("android.intent.action.MAIN"); + + // Going with implicit launch first based on the LAUNCHER category and the app's package. + args.push_back("-c"); + args.push_back("android.intent.category.LAUNCHER"); + args.push_back(get_package_name(p_preset, package_name)); output.clear(); err = OS::get_singleton()->execute(adb, args, &output, &rv, true); print_verbose(output); + if (err || rv != 0 || output.contains("Error: Activity not started")) { + // The implicit launch failed, let's try an explicit launch by specifying the component name before giving up. + const String component_name = get_package_name(p_preset, package_name) + "/com.godot.game.GodotApp"; + print_line("Implicit launch failed... Trying explicit launch using", component_name); + args.erase(get_package_name(p_preset, package_name)); + args.push_back("-n"); + args.push_back(component_name); - if (err || rv != 0 || output.begins_with("Error: Activity not started")) { - add_message(EXPORT_MESSAGE_ERROR, TTR("Run"), TTR("Could not execute on device.")); - CLEANUP_AND_RETURN(ERR_CANT_CREATE); + output.clear(); + err = OS::get_singleton()->execute(adb, args, &output, &rv, true); + print_verbose(output); + + if (err || rv != 0 || output.begins_with("Error: Activity not started")) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Run"), TTR("Could not execute on device.")); + CLEANUP_AND_RETURN(ERR_CANT_CREATE); + } } } @@ -4255,6 +4337,7 @@ void EditorExportPlatformAndroid::initialize() { _update_preset_status(); check_for_changes_thread.start(_check_for_changes_poll_thread, this); #endif + use_scrcpy = EditorSettings::get_singleton()->get_project_metadata("android", "use_scrcpy", false); } } diff --git a/platform/android/export/export_plugin.h b/platform/android/export/export_plugin.h index f640058aa31..4fab3f84a9e 100644 --- a/platform/android/export/export_plugin.h +++ b/platform/android/export/export_plugin.h @@ -93,6 +93,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { uint64_t last_gradle_build_time = 0; String last_gradle_build_dir; + bool use_scrcpy = false; Vector devices; SafeFlag devices_changed; Mutex device_lock; @@ -220,6 +221,10 @@ public: virtual int get_options_count() const override; + virtual Ref get_option_icon(int p_index) const override; + + virtual bool is_option_runnable(int p_index) const override { return p_index != 0; } + virtual String get_options_tooltip() const override; virtual String get_option_label(int p_index) const override;