Przeglądaj źródła

[Android] Add export option to use "scrcpy" to run project from editor.

Pāvels Nadtočajevs 5 miesięcy temu
rodzic
commit
f7263b9517

+ 5 - 0
drivers/unix/os_unix.cpp

@@ -756,6 +756,11 @@ Dictionary OS_Unix::execute_with_pipe(const String &p_path, const List<String> &
 	}
 
 	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<CharString> cs;
 		cs.push_back(p_path.utf8());

+ 1 - 0
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<Texture2D> get_option_icon(int p_index) const;
 	virtual String get_option_label(int p_device) const { return ""; }

+ 2 - 0
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";

+ 4 - 2
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<EditorExportPlatform::DebugFlags> flags = 0;
 

+ 13 - 0
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);

+ 131 - 48
platform/android/export/export_plugin.cpp

@@ -42,6 +42,7 @@
 #include "drivers/png/png_driver_common.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"
@@ -2284,7 +2285,16 @@ bool EditorExportPlatformAndroid::poll_export() {
 
 int EditorExportPlatformAndroid::get_options_count() const {
 	MutexLock lock(device_lock);
-	return devices.size();
+	return devices.size() + 1;
+}
+
+Ref<Texture2D> EditorExportPlatformAndroid::get_option_icon(int p_index) const {
+	if (p_index == 0) {
+		Ref<Theme> theme = EditorNode::get_singleton()->get_editor_theme();
+		ERR_FAIL_COND_V(theme.is_null(), Ref<ImageTexture>());
+		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 {
@@ -2292,32 +2302,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<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> 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;
@@ -2328,7 +2353,7 @@ Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &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();
 
@@ -2339,7 +2364,7 @@ Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &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);
@@ -2377,12 +2402,12 @@ Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &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");
 		}
@@ -2393,16 +2418,16 @@ Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &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");
 	}
@@ -2425,7 +2450,7 @@ Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &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();
@@ -2436,7 +2461,7 @@ Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &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));
@@ -2452,7 +2477,7 @@ Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &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));
@@ -2475,42 +2500,99 @@ Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &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<FileAccess> fa_out = data["stdio"];
+		Ref<FileAccess> 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);
+			}
 		}
 	}
 
@@ -4274,6 +4356,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);
 	}
 }
 

+ 5 - 0
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<Device> devices;
 	SafeFlag devices_changed;
 	Mutex device_lock;
@@ -222,6 +223,10 @@ public:
 
 	virtual int get_options_count() const override;
 
+	virtual Ref<Texture2D> 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;