diff options
| author | Rémi Verschelde <rverschelde@gmail.com> | 2023-01-13 18:00:18 +0100 | 
|---|---|---|
| committer | Rémi Verschelde <rverschelde@gmail.com> | 2023-01-13 18:00:18 +0100 | 
| commit | 3dffe0b967e1fe1b5407bc02e3308748865ee21d (patch) | |
| tree | cd923cc48249a7a53d3cad987ea25ec6af7cdaf7 | |
| parent | 3c9bf4bc210a8e6a208f30ca59de4d4d7e18c04d (diff) | |
| parent | cebefc9f5d26bc5207e6ba399e67a82f76216f13 (diff) | |
Merge pull request #63312 from bruvzg/one_click
[Export] Add one-click deploy over SSH for the desktop exports.
43 files changed, 1392 insertions, 187 deletions
diff --git a/editor/editor_property_name_processor.cpp b/editor/editor_property_name_processor.cpp index 195457c239..18ba19f5f6 100644 --- a/editor/editor_property_name_processor.cpp +++ b/editor/editor_property_name_processor.cpp @@ -211,6 +211,7 @@ EditorPropertyNameProcessor::EditorPropertyNameProcessor() {  	capitalize_string_remaps["rmb"] = "RMB";  	capitalize_string_remaps["rpc"] = "RPC";  	capitalize_string_remaps["s3tc"] = "S3TC"; +	capitalize_string_remaps["scp"] = "SCP";  	capitalize_string_remaps["sdf"] = "SDF";  	capitalize_string_remaps["sdfgi"] = "SDFGI";  	capitalize_string_remaps["sdk"] = "SDK"; diff --git a/editor/editor_run_native.cpp b/editor/editor_run_native.cpp index b0eb4c3d55..eab4e08cec 100644 --- a/editor/editor_run_native.cpp +++ b/editor/editor_run_native.cpp @@ -154,7 +154,9 @@ Error EditorRunNative::run_native(int p_idx, int p_platform) {  	Error err = eep->run(preset, p_idx, flags);  	result_dialog_log->clear();  	if (eep->fill_log_messages(result_dialog_log, err)) { -		result_dialog->popup_centered_ratio(0.5); +		if (eep->get_worst_message_type() >= EditorExportPlatform::EXPORT_MESSAGE_ERROR) { +			result_dialog->popup_centered_ratio(0.5); +		}  	}  	return err;  } diff --git a/editor/export/editor_export.cpp b/editor/export/editor_export.cpp index f48cabf207..cc96107f97 100644 --- a/editor/export/editor_export.cpp +++ b/editor/export/editor_export.cpp @@ -170,6 +170,12 @@ void EditorExport::_notification(int p_what) {  		case NOTIFICATION_PROCESS: {  			update_export_presets();  		} break; + +		case NOTIFICATION_EXIT_TREE: { +			for (int i = 0; i < export_platforms.size(); i++) { +				export_platforms.write[i]->cleanup(); +			} +		} break;  	}  } diff --git a/editor/export/editor_export_platform.cpp b/editor/export/editor_export_platform.cpp index ef6c835b38..6478f99fb1 100644 --- a/editor/export/editor_export_platform.cpp +++ b/editor/export/editor_export_platform.cpp @@ -1322,6 +1322,121 @@ Error EditorExportPlatform::_add_shared_object(void *p_userdata, const SharedObj  	return OK;  } +void EditorExportPlatform::zip_folder_recursive(zipFile &p_zip, const String &p_root_path, const String &p_folder, const String &p_pkg_name) { +	String dir = p_folder.is_empty() ? p_root_path : p_root_path.path_join(p_folder); + +	Ref<DirAccess> da = DirAccess::open(dir); +	da->list_dir_begin(); +	String f = da->get_next(); +	while (!f.is_empty()) { +		if (f == "." || f == "..") { +			f = da->get_next(); +			continue; +		} +		if (da->is_link(f)) { +			OS::DateTime dt = OS::get_singleton()->get_datetime(); + +			zip_fileinfo zipfi; +			zipfi.tmz_date.tm_year = dt.year; +			zipfi.tmz_date.tm_mon = dt.month - 1; // Note: "tm" month range - 0..11, Godot month range - 1..12, https://www.cplusplus.com/reference/ctime/tm/ +			zipfi.tmz_date.tm_mday = dt.day; +			zipfi.tmz_date.tm_hour = dt.hour; +			zipfi.tmz_date.tm_min = dt.minute; +			zipfi.tmz_date.tm_sec = dt.second; +			zipfi.dosDate = 0; +			// 0120000: symbolic link type +			// 0000755: permissions rwxr-xr-x +			// 0000644: permissions rw-r--r-- +			uint32_t _mode = 0120644; +			zipfi.external_fa = (_mode << 16L) | !(_mode & 0200); +			zipfi.internal_fa = 0; + +			zipOpenNewFileInZip4(p_zip, +					p_folder.path_join(f).utf8().get_data(), +					&zipfi, +					nullptr, +					0, +					nullptr, +					0, +					nullptr, +					Z_DEFLATED, +					Z_DEFAULT_COMPRESSION, +					0, +					-MAX_WBITS, +					DEF_MEM_LEVEL, +					Z_DEFAULT_STRATEGY, +					nullptr, +					0, +					0x0314, // "version made by", 0x03 - Unix, 0x14 - ZIP specification version 2.0, required to store Unix file permissions +					0); + +			String target = da->read_link(f); +			zipWriteInFileInZip(p_zip, target.utf8().get_data(), target.utf8().size()); +			zipCloseFileInZip(p_zip); +		} else if (da->current_is_dir()) { +			zip_folder_recursive(p_zip, p_root_path, p_folder.path_join(f), p_pkg_name); +		} else { +			bool _is_executable = is_executable(dir.path_join(f)); + +			OS::DateTime dt = OS::get_singleton()->get_datetime(); + +			zip_fileinfo zipfi; +			zipfi.tmz_date.tm_year = dt.year; +			zipfi.tmz_date.tm_mon = dt.month - 1; // Note: "tm" month range - 0..11, Godot month range - 1..12, https://www.cplusplus.com/reference/ctime/tm/ +			zipfi.tmz_date.tm_mday = dt.day; +			zipfi.tmz_date.tm_hour = dt.hour; +			zipfi.tmz_date.tm_min = dt.minute; +			zipfi.tmz_date.tm_sec = dt.second; +			zipfi.dosDate = 0; +			// 0100000: regular file type +			// 0000755: permissions rwxr-xr-x +			// 0000644: permissions rw-r--r-- +			uint32_t _mode = (_is_executable ? 0100755 : 0100644); +			zipfi.external_fa = (_mode << 16L) | !(_mode & 0200); +			zipfi.internal_fa = 0; + +			zipOpenNewFileInZip4(p_zip, +					p_folder.path_join(f).utf8().get_data(), +					&zipfi, +					nullptr, +					0, +					nullptr, +					0, +					nullptr, +					Z_DEFLATED, +					Z_DEFAULT_COMPRESSION, +					0, +					-MAX_WBITS, +					DEF_MEM_LEVEL, +					Z_DEFAULT_STRATEGY, +					nullptr, +					0, +					0x0314, // "version made by", 0x03 - Unix, 0x14 - ZIP specification version 2.0, required to store Unix file permissions +					0); + +			Ref<FileAccess> fa = FileAccess::open(dir.path_join(f), FileAccess::READ); +			if (fa.is_null()) { +				add_message(EXPORT_MESSAGE_ERROR, TTR("ZIP Creation"), vformat(TTR("Could not open file to read from path \"%s\"."), dir.path_join(f))); +				return; +			} +			const int bufsize = 16384; +			uint8_t buf[bufsize]; + +			while (true) { +				uint64_t got = fa->get_buffer(buf, bufsize); +				if (got == 0) { +					break; +				} +				zipWriteInFileInZip(p_zip, buf, got); +			} + +			zipCloseFileInZip(p_zip); +		} +		f = da->get_next(); +	} +	da->list_dir_end(); +} +  Error EditorExportPlatform::save_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, Vector<SharedObject> *p_so_files, bool p_embed, int64_t *r_embedded_start, int64_t *r_embedded_size) {  	EditorProgress ep("savepack", TTR("Packing"), 102, true); @@ -1642,5 +1757,123 @@ bool EditorExportPlatform::can_export(const Ref<EditorExportPreset> &p_preset, S  	return valid;  } +Error EditorExportPlatform::ssh_run_on_remote(const String &p_host, const String &p_port, const Vector<String> &p_ssh_args, const String &p_cmd_args, String *r_out, int p_port_fwd) const { +	String ssh_path = EditorSettings::get_singleton()->get("export/ssh/ssh"); +	if (ssh_path.is_empty()) { +		ssh_path = "ssh"; +	} + +	List<String> args; +	args.push_back("-p"); +	args.push_back(p_port); +	for (const String &E : p_ssh_args) { +		args.push_back(E); +	} +	if (p_port_fwd > 0) { +		args.push_back("-R"); +		args.push_back(vformat("%d:localhost:%d", p_port_fwd, p_port_fwd)); +	} +	args.push_back(p_host); +	args.push_back(p_cmd_args); + +	String out; +	int exit_code = -1; + +	if (OS::get_singleton()->is_stdout_verbose()) { +		OS::get_singleton()->print("Executing: %s", ssh_path.utf8().get_data()); +		for (const String &arg : args) { +			OS::get_singleton()->print(" %s", arg.utf8().get_data()); +		} +		OS::get_singleton()->print("\n"); +	} + +	Error err = OS::get_singleton()->execute(ssh_path, args, &out, &exit_code, true); +	if (out.is_empty()) { +		print_verbose(vformat("Exit code: %d", exit_code)); +	} else { +		print_verbose(vformat("Exit code: %d, Output: %s", exit_code, out.replace("\r\n", "\n"))); +	} +	if (r_out) { +		*r_out = out.replace("\r\n", "\n").get_slice("\n", 0); +	} +	if (err != OK) { +		return err; +	} else if (exit_code != 0) { +		if (!out.is_empty()) { +			print_line(out); +		} +		return FAILED; +	} +	return OK; +} + +Error EditorExportPlatform::ssh_run_on_remote_no_wait(const String &p_host, const String &p_port, const Vector<String> &p_ssh_args, const String &p_cmd_args, OS::ProcessID *r_pid, int p_port_fwd) const { +	String ssh_path = EditorSettings::get_singleton()->get("export/ssh/ssh"); +	if (ssh_path.is_empty()) { +		ssh_path = "ssh"; +	} + +	List<String> args; +	args.push_back("-p"); +	args.push_back(p_port); +	for (const String &E : p_ssh_args) { +		args.push_back(E); +	} +	if (p_port_fwd > 0) { +		args.push_back("-R"); +		args.push_back(vformat("%d:localhost:%d", p_port_fwd, p_port_fwd)); +	} +	args.push_back(p_host); +	args.push_back(p_cmd_args); + +	if (OS::get_singleton()->is_stdout_verbose()) { +		OS::get_singleton()->print("Executing: %s", ssh_path.utf8().get_data()); +		for (const String &arg : args) { +			OS::get_singleton()->print(" %s", arg.utf8().get_data()); +		} +		OS::get_singleton()->print("\n"); +	} + +	return OS::get_singleton()->create_process(ssh_path, args, r_pid); +} + +Error EditorExportPlatform::ssh_push_to_remote(const String &p_host, const String &p_port, const Vector<String> &p_scp_args, const String &p_src_file, const String &p_dst_file) const { +	String scp_path = EditorSettings::get_singleton()->get("export/ssh/scp"); +	if (scp_path.is_empty()) { +		scp_path = "scp"; +	} + +	List<String> args; +	args.push_back("-P"); +	args.push_back(p_port); +	for (const String &E : p_scp_args) { +		args.push_back(E); +	} +	args.push_back(p_src_file); +	args.push_back(vformat("%s:%s", p_host, p_dst_file)); + +	String out; +	int exit_code = -1; + +	if (OS::get_singleton()->is_stdout_verbose()) { +		OS::get_singleton()->print("Executing: %s", scp_path.utf8().get_data()); +		for (const String &arg : args) { +			OS::get_singleton()->print(" %s", arg.utf8().get_data()); +		} +		OS::get_singleton()->print("\n"); +	} + +	Error err = OS::get_singleton()->execute(scp_path, args, &out, &exit_code, true); +	if (err != OK) { +		return err; +	} else if (exit_code != 0) { +		if (!out.is_empty()) { +			print_line(out); +		} +		return FAILED; +	} +	return OK; +} +  EditorExportPlatform::EditorExportPlatform() {  } diff --git a/editor/export/editor_export_platform.h b/editor/export/editor_export_platform.h index d3d8a8f186..1fb35759ac 100644 --- a/editor/export/editor_export_platform.h +++ b/editor/export/editor_export_platform.h @@ -35,6 +35,7 @@ class EditorFileSystemDirectory;  struct EditorProgress;  #include "core/io/dir_access.h" +#include "core/io/zip_io.h"  #include "editor_export_preset.h"  #include "editor_export_shared_object.h"  #include "scene/gui/rich_text_label.h" @@ -92,7 +93,6 @@ private:  	void _export_find_resources(EditorFileSystemDirectory *p_dir, HashSet<String> &p_paths);  	void _export_find_dependencies(const String &p_path, HashSet<String> &p_paths); -	void gen_debug_flags(Vector<String> &r_flags, int p_flags);  	static Error _save_pack_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key);  	static Error _save_zip_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key); @@ -126,6 +126,13 @@ protected:  	bool exists_export_template(String template_file_name, String *err) const;  	String find_export_template(String template_file_name, String *err = nullptr) const;  	void gen_export_flags(Vector<String> &r_flags, int p_flags); +	void gen_debug_flags(Vector<String> &r_flags, int p_flags); + +	virtual void zip_folder_recursive(zipFile &p_zip, const String &p_root_path, const String &p_folder, const String &p_pkg_name); + +	Error ssh_run_on_remote(const String &p_host, const String &p_port, const Vector<String> &p_ssh_args, const String &p_cmd_args, String *r_out = nullptr, int p_port_fwd = -1) const; +	Error ssh_run_on_remote_no_wait(const String &p_host, const String &p_port, const Vector<String> &p_ssh_args, const String &p_cmd_args, OS::ProcessID *r_pid = nullptr, int p_port_fwd = -1) const; +	Error ssh_push_to_remote(const String &p_host, const String &p_port, const Vector<String> &p_scp_args, const String &p_src_file, const String &p_dst_file) const;  public:  	virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) const = 0; @@ -215,6 +222,7 @@ public:  		DEBUG_FLAG_VIEW_NAVIGATION = 16,  	}; +	virtual void cleanup() {}  	virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) { return OK; }  	virtual Ref<Texture2D> get_run_icon() const { return get_logo(); } diff --git a/editor/export/editor_export_platform_pc.h b/editor/export/editor_export_platform_pc.h index c71a62ee5c..8c24f2fc68 100644 --- a/editor/export/editor_export_platform_pc.h +++ b/editor/export/editor_export_platform_pc.h @@ -62,7 +62,6 @@ public:  	virtual Error modify_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { return OK; };  	virtual Error export_project_data(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags); -	void set_extension(const String &p_extension, const String &p_feature_key = "default");  	void set_name(const String &p_name);  	void set_os_name(const String &p_name); diff --git a/editor/export/editor_export_plugin.cpp b/editor/export/editor_export_plugin.cpp index f34ebfa541..784dbc116a 100644 --- a/editor/export/editor_export_plugin.cpp +++ b/editor/export/editor_export_plugin.cpp @@ -34,6 +34,7 @@  #include "core/io/dir_access.h"  #include "core/io/file_access.h"  #include "editor/editor_paths.h" +#include "editor/editor_settings.h"  #include "editor/export/editor_export_platform.h"  #include "scene/resources/resource_format_text.h" @@ -226,4 +227,8 @@ void EditorExportPlugin::_bind_methods() {  }  EditorExportPlugin::EditorExportPlugin() { +	GLOBAL_DEF("editor/export/convert_text_resources_to_binary", false); + +	EDITOR_DEF("export/ssh/ssh", ""); +	EDITOR_DEF("export/ssh/scp", "");  } diff --git a/methods.py b/methods.py index ee88401671..7ede2592ff 100644 --- a/methods.py +++ b/methods.py @@ -488,29 +488,29 @@ def use_windows_spawn_fix(self, platform=None):  def save_active_platforms(apnames, ap):      for x in ap: -        names = ["logo"] -        if os.path.isfile(x + "/run_icon.png"): -            names.append("run_icon") - -        for name in names: -            pngf = open(x + "/" + name + ".png", "rb") -            b = pngf.read(1) -            str = " /* AUTOGENERATED FILE, DO NOT EDIT */ \n" -            str += " static const unsigned char _" + x[9:] + "_" + name + "[]={" +        svg_names = [] +        if os.path.isfile(x + "/logo.svg"): +            svg_names.append("logo") +        if os.path.isfile(x + "/run_icon.svg"): +            svg_names.append("run_icon") + +        for name in svg_names: +            svgf = open(x + "/" + name + ".svg", "rb") +            b = svgf.read(1) +            svg_str = " /* AUTOGENERATED FILE, DO NOT EDIT */ \n" +            svg_str += " static const char *_" + x[9:] + "_" + name + '_svg = "'              while len(b) == 1: -                str += hex(ord(b)) -                b = pngf.read(1) -                if len(b) == 1: -                    str += "," +                svg_str += "\\" + hex(ord(b))[1:] +                b = svgf.read(1) -            str += "};\n" +            svg_str += '";\n' -            pngf.close() +            svgf.close()              # NOTE: It is safe to generate this file here, since this is still executed serially -            wf = x + "/" + name + ".gen.h" -            with open(wf, "w") as pngw: -                pngw.write(str) +            wf = x + "/" + name + "_svg.gen.h" +            with open(wf, "w") as svgw: +                svgw.write(svg_str)  def no_verbose(sys, env): diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index a602cc7926..bb1ad3d83b 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -43,10 +43,16 @@  #include "editor/editor_log.h"  #include "editor/editor_node.h"  #include "editor/editor_paths.h" +#include "editor/editor_scale.h"  #include "editor/editor_settings.h"  #include "main/splash.gen.h" -#include "platform/android/logo.gen.h" -#include "platform/android/run_icon.gen.h" +#include "platform/android/logo_svg.gen.h" +#include "platform/android/run_icon_svg.gen.h" + +#include "modules/modules_enabled.gen.h" // For svg. +#ifdef MODULE_SVG_ENABLED +#include "modules/svg/image_loader_svg.h" +#endif  #include <string.h> @@ -3238,8 +3244,17 @@ void EditorExportPlatformAndroid::resolve_platform_feature_priorities(const Ref<  }  EditorExportPlatformAndroid::EditorExportPlatformAndroid() { -	logo = ImageTexture::create_from_image(memnew(Image(_android_logo))); -	run_icon = ImageTexture::create_from_image(memnew(Image(_android_run_icon))); +#ifdef MODULE_SVG_ENABLED +	Ref<Image> img = memnew(Image); +	const bool upsample = !Math::is_equal_approx(Math::round(EDSCALE), EDSCALE); + +	ImageLoaderSVG img_loader; +	img_loader.create_image_from_string(img, _android_logo_svg, EDSCALE, upsample, false); +	logo = ImageTexture::create_from_image(img); + +	img_loader.create_image_from_string(img, _android_run_icon_svg, EDSCALE, upsample, false); +	run_icon = ImageTexture::create_from_image(img); +#endif  	devices_changed.set();  	plugins_changed.set(); diff --git a/platform/android/logo.png b/platform/android/logo.png Binary files differdeleted file mode 100644 index 9c8be93646..0000000000 --- a/platform/android/logo.png +++ /dev/null diff --git a/platform/android/logo.svg b/platform/android/logo.svg new file mode 100644 index 0000000000..f154e55d11 --- /dev/null +++ b/platform/android/logo.svg @@ -0,0 +1 @@ +<svg height="32" width="32" xmlns="http://www.w3.org/2000/svg"><path d="M22.904 20.192a1.25 1.25 0 1 1 1.25-1.25 1.25 1.25 0 0 1-1.25 1.25m-13.808 0a1.25 1.25 0 1 1 1.25-1.25 1.25 1.25 0 0 1-1.25 1.25m14.256-7.525 2.496-4.323a.52.52 0 1 0-.899-.52l-2.528 4.378a15.69 15.69 0 0 0-12.842 0L7.051 7.823a.52.52 0 1 0-.9.521l2.497 4.323C4.361 15 1.43 19.34 1 24.467h30c-.43-5.128-3.361-9.468-7.648-11.8" fill="#56d881"/></svg> diff --git a/platform/android/run_icon.png b/platform/android/run_icon.png Binary files differdeleted file mode 100644 index b687c9ac31..0000000000 --- a/platform/android/run_icon.png +++ /dev/null diff --git a/platform/android/run_icon.svg b/platform/android/run_icon.svg new file mode 100644 index 0000000000..24d930fece --- /dev/null +++ b/platform/android/run_icon.svg @@ -0,0 +1 @@ +<svg height="16" width="16" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path d="M11.187 9.936a.577.577 0 1 1 .578-.578.577.577 0 0 1-.578.578m-6.374 0a.577.577 0 1 1 .577-.578.577.577 0 0 1-.577.578m6.581-3.475 1.153-1.996a.24.24 0 1 0-.415-.24l-1.167 2.021a7.244 7.244 0 0 0-5.93 0L3.868 4.225a.24.24 0 1 0-.415.24l1.153 1.996a6.806 6.806 0 0 0-3.532 5.448h13.852a6.807 6.807 0 0 0-3.532-5.448" fill="#56d881" style="fill:#e0e0e0;fill-opacity:1"/></svg> diff --git a/platform/ios/export/export_plugin.cpp b/platform/ios/export/export_plugin.cpp index 43eb0f4b1f..ccd3480d11 100644 --- a/platform/ios/export/export_plugin.cpp +++ b/platform/ios/export/export_plugin.cpp @@ -32,6 +32,13 @@  #include "core/string/translation.h"  #include "editor/editor_node.h" +#include "editor/editor_scale.h" +#include "platform/ios/logo_svg.gen.h" + +#include "modules/modules_enabled.gen.h" // For svg. +#ifdef MODULE_SVG_ENABLED +#include "modules/svg/image_loader_svg.h" +#endif  void EditorExportPlatformIOS::get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) const {  	// Vulkan and OpenGL ES 3.0 both mandate ETC2 support. @@ -1926,7 +1933,15 @@ bool EditorExportPlatformIOS::has_valid_project_configuration(const Ref<EditorEx  }  EditorExportPlatformIOS::EditorExportPlatformIOS() { -	logo = ImageTexture::create_from_image(memnew(Image(_ios_logo))); +#ifdef MODULE_SVG_ENABLED +	Ref<Image> img = memnew(Image); +	const bool upsample = !Math::is_equal_approx(Math::round(EDSCALE), EDSCALE); + +	ImageLoaderSVG img_loader; +	img_loader.create_image_from_string(img, _ios_logo_svg, EDSCALE, upsample, false); +	logo = ImageTexture::create_from_image(img); +#endif +  	plugins_changed.set();  #ifndef ANDROID_ENABLED  	check_for_changes_thread.start(_check_for_changes_poll_thread, this); diff --git a/platform/ios/export/export_plugin.h b/platform/ios/export/export_plugin.h index deadec4b43..628dae2e6f 100644 --- a/platform/ios/export/export_plugin.h +++ b/platform/ios/export/export_plugin.h @@ -43,7 +43,6 @@  #include "editor/editor_settings.h"  #include "editor/export/editor_export_platform.h"  #include "main/splash.gen.h" -#include "platform/ios/logo.gen.h"  #include "string.h"  #include "godot_plugin_config.h" diff --git a/platform/ios/logo.png b/platform/ios/logo.png Binary files differdeleted file mode 100644 index 966d8aa70a..0000000000 --- a/platform/ios/logo.png +++ /dev/null diff --git a/platform/ios/logo.svg b/platform/ios/logo.svg new file mode 100644 index 0000000000..47a72bcf49 --- /dev/null +++ b/platform/ios/logo.svg @@ -0,0 +1 @@ +<svg height="32" width="32" xmlns="http://www.w3.org/2000/svg"><path d="M1 23.27h2.504V12.61H1zm1.247-12.057c.784 0 1.398-.603 1.398-1.358 0-.764-.614-1.367-1.398-1.367-.774 0-1.388.603-1.388 1.367 0 .755.614 1.358 1.388 1.358zm9.594-2.695c-4.233 0-6.888 2.886-6.888 7.502s2.654 7.492 6.888 7.492c4.224 0 6.88-2.876 6.88-7.492s-2.656-7.502-6.88-7.502zm0 2.212c2.585 0 4.234 2.052 4.234 5.29 0 3.228-1.649 5.28-4.234 5.28-2.594 0-4.233-2.052-4.233-5.28 0-3.238 1.639-5.29 4.233-5.29zm7.936 8.458c.11 2.675 2.303 4.324 5.641 4.324 3.51 0 5.723-1.73 5.723-4.485 0-2.162-1.247-3.379-4.194-4.053l-1.67-.382c-1.78-.422-2.513-.985-2.513-1.95 0-1.208 1.106-2.012 2.745-2.012 1.66 0 2.796.814 2.916 2.172H30.9c-.06-2.554-2.172-4.284-5.37-4.284-3.158 0-5.4 1.74-5.4 4.314 0 2.072 1.267 3.36 3.942 3.973l1.88.442c1.83.433 2.575 1.036 2.575 2.082 0 1.207-1.217 2.072-2.967 2.072-1.77 0-3.107-.875-3.268-2.213h-2.514z" fill="#bfbfbf"/></svg> diff --git a/platform/linuxbsd/export/export.cpp b/platform/linuxbsd/export/export.cpp index e6925a2011..2c5a945b6c 100644 --- a/platform/linuxbsd/export/export.cpp +++ b/platform/linuxbsd/export/export.cpp @@ -36,7 +36,6 @@  void register_linuxbsd_exporter() {  	Ref<EditorExportPlatformLinuxBSD> platform;  	platform.instantiate(); -	platform->set_logo(ImageTexture::create_from_image(memnew(Image(_linuxbsd_logo))));  	platform->set_name("Linux/X11");  	platform->set_os_name("Linux");  	platform->set_chmod_flags(0755); diff --git a/platform/linuxbsd/export/export_plugin.cpp b/platform/linuxbsd/export/export_plugin.cpp index f6e59bf465..c900cad007 100644 --- a/platform/linuxbsd/export/export_plugin.cpp +++ b/platform/linuxbsd/export/export_plugin.cpp @@ -32,6 +32,15 @@  #include "core/config/project_settings.h"  #include "editor/editor_node.h" +#include "editor/editor_paths.h" +#include "editor/editor_scale.h" +#include "platform/linuxbsd/logo_svg.gen.h" +#include "platform/linuxbsd/run_icon_svg.gen.h" + +#include "modules/modules_enabled.gen.h" // For svg. +#ifdef MODULE_SVG_ENABLED +#include "modules/svg/image_loader_svg.h" +#endif  Error EditorExportPlatformLinuxBSD::_export_debug_script(const Ref<EditorExportPreset> &p_preset, const String &p_app_name, const String &p_pkg_name, const String &p_path) {  	Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::WRITE); @@ -49,26 +58,47 @@ Error EditorExportPlatformLinuxBSD::_export_debug_script(const Ref<EditorExportP  }  Error EditorExportPlatformLinuxBSD::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { -	Error err = EditorExportPlatformPC::export_project(p_preset, p_debug, p_path, p_flags); - -	if (err != OK) { -		return err; -	} +	bool export_as_zip = p_path.ends_with("zip"); -	String app_name; +	String pkg_name;  	if (String(GLOBAL_GET("application/config/name")) != "") { -		app_name = String(GLOBAL_GET("application/config/name")); +		pkg_name = String(GLOBAL_GET("application/config/name"));  	} else { -		app_name = "Unnamed"; +		pkg_name = "Unnamed"; +	} + +	pkg_name = OS::get_singleton()->get_safe_dir_name(pkg_name); + +	// Setup temp folder. +	String path = p_path; +	String tmp_dir_path = EditorPaths::get_singleton()->get_cache_dir().path_join(pkg_name); + +	Ref<DirAccess> tmp_app_dir = DirAccess::create_for_path(tmp_dir_path); +	if (export_as_zip) { +		if (tmp_app_dir.is_null()) { +			return ERR_CANT_CREATE; +		} +		if (DirAccess::exists(tmp_dir_path)) { +			if (tmp_app_dir->change_dir(tmp_dir_path) == OK) { +				tmp_app_dir->erase_contents_recursive(); +			} +		} +		tmp_app_dir->make_dir_recursive(tmp_dir_path); +		path = tmp_dir_path.path_join(p_path.get_file().get_basename()); +	} + +	// Export project. +	Error err = EditorExportPlatformPC::export_project(p_preset, p_debug, path, p_flags); +	if (err != OK) { +		return err;  	} -	app_name = OS::get_singleton()->get_safe_dir_name(app_name);  	// Save console script.  	if (err == OK) {  		int con_scr = p_preset->get("debug/export_console_script");  		if ((con_scr == 1 && p_debug) || (con_scr == 2)) { -			String scr_path = p_path.get_basename() + ".sh"; -			err = _export_debug_script(p_preset, app_name, p_path.get_file(), scr_path); +			String scr_path = path.get_basename() + ".sh"; +			err = _export_debug_script(p_preset, pkg_name, path.get_file(), scr_path);  			FileAccess::set_unix_permissions(scr_path, 0755);  			if (err != OK) {  				add_message(EXPORT_MESSAGE_ERROR, TTR("Debug Script Export"), TTR("Could not create console script.")); @@ -76,6 +106,27 @@ Error EditorExportPlatformLinuxBSD::export_project(const Ref<EditorExportPreset>  		}  	} +	// ZIP project. +	if (export_as_zip) { +		if (FileAccess::exists(p_path)) { +			OS::get_singleton()->move_to_trash(p_path); +		} + +		Ref<FileAccess> io_fa_dst; +		zlib_filefunc_def io_dst = zipio_create_io(&io_fa_dst); +		zipFile zip = zipOpen2(p_path.utf8().get_data(), APPEND_STATUS_CREATE, nullptr, &io_dst); + +		zip_folder_recursive(zip, tmp_dir_path, "", pkg_name); + +		zipClose(zip, nullptr); + +		if (tmp_app_dir->change_dir(tmp_dir_path) == OK) { +			tmp_app_dir->erase_contents_recursive(); +			tmp_app_dir->change_dir(".."); +			tmp_app_dir->remove(pkg_name); +		} +	} +  	return err;  } @@ -86,12 +137,51 @@ String EditorExportPlatformLinuxBSD::get_template_file_name(const String &p_targ  List<String> EditorExportPlatformLinuxBSD::get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const {  	List<String> list;  	list.push_back(p_preset->get("binary_format/architecture")); +	list.push_back("zip"); +  	return list;  }  void EditorExportPlatformLinuxBSD::get_export_options(List<ExportOption> *r_options) {  	EditorExportPlatformPC::get_export_options(r_options); +  	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "binary_format/architecture", PROPERTY_HINT_ENUM, "x86_64,x86_32,arm64,arm32,rv64,ppc64,ppc32"), "x86_64")); + +	String run_script = "#!/usr/bin/env bash\n" +						"export DISPLAY=:0\n" +						"unzip -o -q \"{temp_dir}/{archive_name}\" -d \"{temp_dir}\"\n" +						"\"{temp_dir}/{exe_name}\" {cmd_args}"; + +	String cleanup_script = "#!/usr/bin/env bash\n" +							"kill $(pgrep -x -f \"{temp_dir}/{exe_name} {cmd_args}\")\n" +							"rm -rf \"{temp_dir}\""; + +	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "ssh_remote_deploy/enabled"), false)); +	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "ssh_remote_deploy/host"), "user@host_ip")); +	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "ssh_remote_deploy/port"), "22")); + +	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "ssh_remote_deploy/extra_args_ssh", PROPERTY_HINT_MULTILINE_TEXT), "")); +	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "ssh_remote_deploy/extra_args_scp", PROPERTY_HINT_MULTILINE_TEXT), "")); +	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "ssh_remote_deploy/run_script", PROPERTY_HINT_MULTILINE_TEXT), run_script)); +	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "ssh_remote_deploy/cleanup_script", PROPERTY_HINT_MULTILINE_TEXT), cleanup_script)); +} + +bool EditorExportPlatformLinuxBSD::is_elf(const String &p_path) const { +	Ref<FileAccess> fb = FileAccess::open(p_path, FileAccess::READ); +	ERR_FAIL_COND_V_MSG(fb.is_null(), false, vformat("Can't open file: \"%s\".", p_path)); +	uint32_t magic = fb->get_32(); +	return (magic == 0x464c457f); +} + +bool EditorExportPlatformLinuxBSD::is_shebang(const String &p_path) const { +	Ref<FileAccess> fb = FileAccess::open(p_path, FileAccess::READ); +	ERR_FAIL_COND_V_MSG(fb.is_null(), false, vformat("Can't open file: \"%s\".", p_path)); +	uint16_t magic = fb->get_16(); +	return (magic == 0x2123); +} + +bool EditorExportPlatformLinuxBSD::is_executable(const String &p_path) const { +	return is_elf(p_path) || is_shebang(p_path);  }  Error EditorExportPlatformLinuxBSD::fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size) { @@ -200,3 +290,235 @@ Error EditorExportPlatformLinuxBSD::fixup_embedded_pck(const String &p_path, int  	}  	return OK;  } + +Ref<Texture2D> EditorExportPlatformLinuxBSD::get_run_icon() const { +	return run_icon; +} + +bool EditorExportPlatformLinuxBSD::poll_export() { +	Ref<EditorExportPreset> preset; + +	for (int i = 0; i < EditorExport::get_singleton()->get_export_preset_count(); i++) { +		Ref<EditorExportPreset> ep = EditorExport::get_singleton()->get_export_preset(i); +		if (ep->is_runnable() && ep->get_platform() == this) { +			preset = ep; +			break; +		} +	} + +	int prev = menu_options; +	menu_options = (preset.is_valid() && preset->get("ssh_remote_deploy/enabled").operator bool()); +	if (ssh_pid != 0 || !cleanup_commands.is_empty()) { +		if (menu_options == 0) { +			cleanup(); +		} else { +			menu_options += 1; +		} +	} +	return menu_options != prev; +} + +Ref<ImageTexture> EditorExportPlatformLinuxBSD::get_option_icon(int p_index) const { +	return p_index == 1 ? stop_icon : EditorExportPlatform::get_option_icon(p_index); +} + +int EditorExportPlatformLinuxBSD::get_options_count() const { +	return menu_options; +} + +String EditorExportPlatformLinuxBSD::get_option_label(int p_index) const { +	return (p_index) ? TTR("Stop and uninstall") : TTR("Run on remote Linux/BSD system"); +} + +String EditorExportPlatformLinuxBSD::get_option_tooltip(int p_index) const { +	return (p_index) ? TTR("Stop and uninstall running project from the remote system") : TTR("Run exported project on remote Linux/BSD system"); +} + +void EditorExportPlatformLinuxBSD::cleanup() { +	if (ssh_pid != 0 && OS::get_singleton()->is_process_running(ssh_pid)) { +		print_line("Terminating connection..."); +		OS::get_singleton()->kill(ssh_pid); +		OS::get_singleton()->delay_usec(1000); +	} + +	if (!cleanup_commands.is_empty()) { +		print_line("Stopping and deleting previous version..."); +		for (const SSHCleanupCommand &cmd : cleanup_commands) { +			if (cmd.wait) { +				ssh_run_on_remote(cmd.host, cmd.port, cmd.ssh_args, cmd.cmd_args); +			} else { +				ssh_run_on_remote_no_wait(cmd.host, cmd.port, cmd.ssh_args, cmd.cmd_args); +			} +		} +	} +	ssh_pid = 0; +	cleanup_commands.clear(); +} + +Error EditorExportPlatformLinuxBSD::run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) { +	cleanup(); +	if (p_device) { // Stop command, cleanup only. +		return OK; +	} + +	EditorProgress ep("run", TTR("Running..."), 5); + +	const String dest = EditorPaths::get_singleton()->get_cache_dir().path_join("linuxbsd"); +	Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); +	if (!da->dir_exists(dest)) { +		Error err = da->make_dir_recursive(dest); +		if (err != OK) { +			EditorNode::get_singleton()->show_warning(TTR("Could not create temp directory:") + "\n" + dest); +			return err; +		} +	} + +	String host = p_preset->get("ssh_remote_deploy/host").operator String(); +	String port = p_preset->get("ssh_remote_deploy/port").operator String(); +	if (port.is_empty()) { +		port = "22"; +	} +	Vector<String> extra_args_ssh = p_preset->get("ssh_remote_deploy/extra_args_ssh").operator String().split(" "); +	Vector<String> extra_args_scp = p_preset->get("ssh_remote_deploy/extra_args_scp").operator String().split(" "); + +	const String basepath = dest.path_join("tmp_linuxbsd_export"); + +#define CLEANUP_AND_RETURN(m_err)                      \ +	{                                                  \ +		if (da->file_exists(basepath + ".zip")) {      \ +			da->remove(basepath + ".zip");             \ +		}                                              \ +		if (da->file_exists(basepath + "_start.sh")) { \ +			da->remove(basepath + "_start.sh");        \ +		}                                              \ +		if (da->file_exists(basepath + "_clean.sh")) { \ +			da->remove(basepath + "_clean.sh");        \ +		}                                              \ +		return m_err;                                  \ +	}                                                  \ +	((void)0) + +	if (ep.step(TTR("Exporting project..."), 1)) { +		return ERR_SKIP; +	} +	Error err = export_project(p_preset, true, basepath + ".zip", p_debug_flags); +	if (err != OK) { +		DirAccess::remove_file_or_error(basepath + ".zip"); +		return err; +	} + +	String cmd_args; +	{ +		Vector<String> cmd_args_list; +		gen_debug_flags(cmd_args_list, p_debug_flags); +		for (int i = 0; i < cmd_args_list.size(); i++) { +			if (i != 0) { +				cmd_args += " "; +			} +			cmd_args += cmd_args_list[i]; +		} +	} + +	const bool use_remote = (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) || (p_debug_flags & DEBUG_FLAG_DUMB_CLIENT); +	int dbg_port = EditorSettings::get_singleton()->get("network/debug/remote_port"); + +	print_line("Creating temporary directory..."); +	ep.step(TTR("Creating temporary directory..."), 2); +	String temp_dir; +	err = ssh_run_on_remote(host, port, extra_args_ssh, "mktemp -d", &temp_dir); +	if (err != OK || temp_dir.is_empty()) { +		CLEANUP_AND_RETURN(err); +	} + +	print_line("Uploading archive..."); +	ep.step(TTR("Uploading archive..."), 3); +	err = ssh_push_to_remote(host, port, extra_args_scp, basepath + ".zip", temp_dir); +	if (err != OK) { +		CLEANUP_AND_RETURN(err); +	} + +	{ +		String run_script = p_preset->get("ssh_remote_deploy/run_script"); +		run_script = run_script.replace("{temp_dir}", temp_dir); +		run_script = run_script.replace("{archive_name}", basepath.get_file() + ".zip"); +		run_script = run_script.replace("{exe_name}", basepath.get_file()); +		run_script = run_script.replace("{cmd_args}", cmd_args); + +		Ref<FileAccess> f = FileAccess::open(basepath + "_start.sh", FileAccess::WRITE); +		if (f.is_null()) { +			CLEANUP_AND_RETURN(err); +		} + +		f->store_string(run_script); +	} + +	{ +		String clean_script = p_preset->get("ssh_remote_deploy/cleanup_script"); +		clean_script = clean_script.replace("{temp_dir}", temp_dir); +		clean_script = clean_script.replace("{archive_name}", basepath.get_file() + ".zip"); +		clean_script = clean_script.replace("{exe_name}", basepath.get_file()); +		clean_script = clean_script.replace("{cmd_args}", cmd_args); + +		Ref<FileAccess> f = FileAccess::open(basepath + "_clean.sh", FileAccess::WRITE); +		if (f.is_null()) { +			CLEANUP_AND_RETURN(err); +		} + +		f->store_string(clean_script); +	} + +	print_line("Uploading scripts..."); +	ep.step(TTR("Uploading scripts..."), 4); +	err = ssh_push_to_remote(host, port, extra_args_scp, basepath + "_start.sh", temp_dir); +	if (err != OK) { +		CLEANUP_AND_RETURN(err); +	} +	err = ssh_run_on_remote(host, port, extra_args_ssh, vformat("chmod +x \"%s/%s\"", temp_dir, basepath.get_file() + "_start.sh")); +	if (err != OK || temp_dir.is_empty()) { +		CLEANUP_AND_RETURN(err); +	} +	err = ssh_push_to_remote(host, port, extra_args_scp, basepath + "_clean.sh", temp_dir); +	if (err != OK) { +		CLEANUP_AND_RETURN(err); +	} +	err = ssh_run_on_remote(host, port, extra_args_ssh, vformat("chmod +x \"%s/%s\"", temp_dir, basepath.get_file() + "_clean.sh")); +	if (err != OK || temp_dir.is_empty()) { +		CLEANUP_AND_RETURN(err); +	} + +	print_line("Starting project..."); +	ep.step(TTR("Starting project..."), 5); +	err = ssh_run_on_remote_no_wait(host, port, extra_args_ssh, vformat("\"%s/%s\"", temp_dir, basepath.get_file() + "_start.sh"), &ssh_pid, (use_remote) ? dbg_port : -1); +	if (err != OK) { +		CLEANUP_AND_RETURN(err); +	} + +	cleanup_commands.clear(); +	cleanup_commands.push_back(SSHCleanupCommand(host, port, extra_args_ssh, vformat("\"%s/%s\"", temp_dir, basepath.get_file() + "_clean.sh"))); + +	print_line("Project started."); + +	CLEANUP_AND_RETURN(OK); +#undef CLEANUP_AND_RETURN +} + +EditorExportPlatformLinuxBSD::EditorExportPlatformLinuxBSD() { +#ifdef MODULE_SVG_ENABLED +	Ref<Image> img = memnew(Image); +	const bool upsample = !Math::is_equal_approx(Math::round(EDSCALE), EDSCALE); + +	ImageLoaderSVG img_loader; +	img_loader.create_image_from_string(img, _linuxbsd_logo_svg, EDSCALE, upsample, false); +	set_logo(ImageTexture::create_from_image(img)); + +	img_loader.create_image_from_string(img, _linuxbsd_run_icon_svg, EDSCALE, upsample, false); +	run_icon = ImageTexture::create_from_image(img); +#endif + +	Ref<Theme> theme = EditorNode::get_singleton()->get_editor_theme(); +	if (theme.is_valid()) { +		stop_icon = theme->get_icon(SNAME("Stop"), SNAME("EditorIcons")); +	} else { +		stop_icon.instantiate(); +	} +} diff --git a/platform/linuxbsd/export/export_plugin.h b/platform/linuxbsd/export/export_plugin.h index 71cf18ff3e..4f860c3fd0 100644 --- a/platform/linuxbsd/export/export_plugin.h +++ b/platform/linuxbsd/export/export_plugin.h @@ -34,19 +34,58 @@  #include "core/io/file_access.h"  #include "editor/editor_settings.h"  #include "editor/export/editor_export_platform_pc.h" -#include "platform/linuxbsd/logo.gen.h"  #include "scene/resources/texture.h"  class EditorExportPlatformLinuxBSD : public EditorExportPlatformPC { +	HashMap<String, String> extensions; + +	struct SSHCleanupCommand { +		String host; +		String port; +		Vector<String> ssh_args; +		String cmd_args; +		bool wait = false; + +		SSHCleanupCommand(){}; +		SSHCleanupCommand(const String &p_host, const String &p_port, const Vector<String> &p_ssh_arg, const String &p_cmd_args, bool p_wait = false) { +			host = p_host; +			port = p_port; +			ssh_args = p_ssh_arg; +			cmd_args = p_cmd_args; +			wait = p_wait; +		}; +	}; + +	Ref<ImageTexture> run_icon; +	Ref<ImageTexture> stop_icon; + +	Vector<SSHCleanupCommand> cleanup_commands; +	OS::ProcessID ssh_pid = 0; +	int menu_options = 0; + +	bool is_elf(const String &p_path) const; +	bool is_shebang(const String &p_path) const; +  	Error _export_debug_script(const Ref<EditorExportPreset> &p_preset, const String &p_app_name, const String &p_pkg_name, const String &p_path);  public: -	void set_extension(const String &p_extension, const String &p_feature_key = "default"); -	virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override;  	virtual void get_export_options(List<ExportOption> *r_options) override; +	virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override;  	virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override;  	virtual String get_template_file_name(const String &p_target, const String &p_arch) const override;  	virtual Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size) override; +	virtual bool is_executable(const String &p_path) const override; + +	virtual Ref<Texture2D> get_run_icon() const override; +	virtual bool poll_export() override; +	virtual Ref<ImageTexture> get_option_icon(int p_index) const override; +	virtual int get_options_count() const override; +	virtual String get_option_label(int p_index) const override; +	virtual String get_option_tooltip(int p_index) const override; +	virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) override; +	virtual void cleanup() override; + +	EditorExportPlatformLinuxBSD();  };  #endif // LINUXBSD_EXPORT_PLUGIN_H diff --git a/platform/linuxbsd/logo.png b/platform/linuxbsd/logo.png Binary files differdeleted file mode 100644 index 078654b757..0000000000 --- a/platform/linuxbsd/logo.png +++ /dev/null diff --git a/platform/linuxbsd/logo.svg b/platform/linuxbsd/logo.svg new file mode 100644 index 0000000000..e5f9f03e0c --- /dev/null +++ b/platform/linuxbsd/logo.svg @@ -0,0 +1 @@ +<svg height="32" width="32"><path d="M13 31h6s3 0 6-6c2.864-5.727-6-17-6-17h-6S3.775 18.55 7 25c3 6 6 6 6 6z" fill="#fff"/><path d="M15.876 28.636c-.05.322-.116.637-.204.941-.142.496-.35.993-.659 1.416.32.02.649.023.985.007a9.1 9.1 0 0 0 .985-.007c-.309-.423-.516-.92-.659-1.416a7.666 7.666 0 0 1-.203-.94l-.123.003c-.04 0-.081-.003-.122-.004z" fill="#333"/><path d="M21.693 21.916c-.629.01-.934.633-1.497.7-.694.08-1.128-.722-2.11-.123-.98.6-1.826 7.473.45 8.409 2.274.935 6.506-4.545 6.23-5.662-.275-1.116-1.146-.853-1.582-1.399-.436-.545.003-1.41-.995-1.82a1.246 1.246 0 0 0-.496-.105zm-11.461 0a1.315 1.315 0 0 0-.421.105c-.998.41-.56 1.275-.995 1.82-.436.546-1.31.283-1.586 1.4-.275 1.116 3.956 6.596 6.232 5.66 2.275-.935 1.429-7.808.448-8.408-.981-.6-1.415.204-2.11.122-.584-.068-.888-.739-1.568-.7z" fill="#f4bb37"/><path d="M15.998.99c-2.934 0-4.657 1.79-4.982 4.204-.324 2.414.198 2.856-.614 5.328-.813 2.472-4.456 6.71-4.37 10.62.026 1.217.166 2.27.41 3.192.3-.496.743-.846 1.066-.995.253-.117.375-.173.432-.194.008-.062.04-.205.098-.485.08-.386.387-.99.91-1.386-.005-.12-.01-.239-.013-.363-.06-3.033 3.073-6.318 3.65-8.236.577-1.917.326-2.114.421-2.59.096-.477.463-1.032.992-1.475a.23.23 0 0 1 .15-.06c.482-.005.965 1.75 1.898 1.752.933 0 1.419-2.141 1.956-1.692.529.443.896.998.992 1.474.095.477-.156.674.42 2.591.578 1.918 3.708 5.203 3.648 8.236-.003.123-.008.24-.014.36.526.396.834 1.002.914 1.389.058.28.09.423.098.485.057.021.18.08.432.197.323.15.764.499 1.063.995.244-.922.387-1.976.414-3.195.085-3.91-3.562-8.148-4.374-10.62-.813-2.472-.287-2.914-.611-5.328C20.659 2.78 18.933.99 15.998.99z" fill="#333"/></svg> diff --git a/platform/linuxbsd/run_icon.svg b/platform/linuxbsd/run_icon.svg new file mode 100644 index 0000000000..56465a0df3 --- /dev/null +++ b/platform/linuxbsd/run_icon.svg @@ -0,0 +1 @@ +<svg height="16" width="16" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path d="M7.941 13.966a3.62 3.62 0 0 1-.096.444 2.129 2.129 0 0 1-.31.668c.15.01.305.01.464.003.16.008.314.007.465-.003a2.129 2.129 0 0 1-.31-.668 3.62 3.62 0 0 1-.097-.444l-.058.001-.058-.001z" fill="#333" style="stroke-width:.472092;fill:#e0e0e0;fill-opacity:1"/><path d="M10.688 10.793c-.297.005-.441.299-.707.33-.328.038-.533-.34-.996-.058-.463.283-.862 3.528.212 3.97 1.074.442 3.072-2.146 2.942-2.673-.13-.527-.542-.403-.747-.66-.206-.258 0-.666-.47-.86a.588.588 0 0 0-.234-.05zm-5.411 0a.62.62 0 0 0-.199.05c-.47.193-.264.601-.47.859-.205.257-.618.133-.748.66s1.867 3.115 2.942 2.673c1.074-.442.674-3.687.211-3.97-.463-.283-.668.096-.995.058-.277-.032-.42-.349-.741-.33z" fill="#f4bb37" style="stroke-width:.472092;fill:#e0e0e0;fill-opacity:1"/><path d="M8 .914c-1.386 0-2.2.845-2.353 1.985-.153 1.14.094 1.348-.29 2.515s-2.103 3.168-2.063 5.013c.012.575.078 1.072.194 1.507a1.25 1.25 0 0 1 .503-.47 4.37 4.37 0 0 1 .204-.09c.004-.03.019-.098.046-.23.038-.182.183-.467.43-.654a4.773 4.773 0 0 1-.006-.172c-.029-1.431 1.45-2.982 1.723-3.888.272-.905.154-.998.199-1.223.045-.225.218-.487.468-.696a.11.11 0 0 1 .07-.028c.228-.003.456.826.897.827.44 0 .67-1.01.923-.799.25.21.423.471.468.696.045.225-.073.318.199 1.223.272.906 1.75 2.457 1.722 3.888-.001.058-.004.114-.007.17a1.2 1.2 0 0 1 .432.656c.027.132.042.2.046.23.027.01.085.037.204.092.153.07.36.236.502.47.115-.435.183-.933.195-1.509.04-1.845-1.681-3.846-2.065-5.013-.383-1.167-.135-1.376-.288-2.515C10.2 1.759 9.385.914 7.999.914Z" fill="#333" style="stroke-width:.472092;fill:#e0e0e0;fill-opacity:1"/></svg> diff --git a/platform/macos/export/export_plugin.cpp b/platform/macos/export/export_plugin.cpp index 311619f657..73fbd45400 100644 --- a/platform/macos/export/export_plugin.cpp +++ b/platform/macos/export/export_plugin.cpp @@ -38,8 +38,14 @@  #include "core/string/translation.h"  #include "editor/editor_node.h"  #include "editor/editor_paths.h" +#include "editor/editor_scale.h" +#include "platform/macos/logo_svg.gen.h" +#include "platform/macos/run_icon_svg.gen.h" -#include "modules/modules_enabled.gen.h" // For regex. +#include "modules/modules_enabled.gen.h" // For svg and regex. +#ifdef MODULE_SVG_ENABLED +#include "modules/svg/image_loader_svg.h" +#endif  void EditorExportPlatformMacOS::get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) const {  	if (p_preset->get("texture_format/s3tc")) { @@ -207,6 +213,23 @@ void EditorExportPlatformMacOS::get_export_options(List<ExportOption> *r_options  	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/s3tc"), true));  	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/etc"), false));  	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/etc2"), false)); + +	String run_script = "#!/usr/bin/env bash\n" +						"unzip -o -q \"{temp_dir}/{archive_name}\" -d \"{temp_dir}\"\n" +						"open \"{temp_dir}/{exe_name}.app\" --args {cmd_args}"; + +	String cleanup_script = "#!/usr/bin/env bash\n" +							"kill $(pgrep -x -f \"{temp_dir}/{exe_name}.app/Contents/MacOS/{exe_name} {cmd_args}\")\n" +							"rm -rf \"{temp_dir}\""; + +	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "ssh_remote_deploy/enabled"), false)); +	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "ssh_remote_deploy/host"), "user@host_ip")); +	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "ssh_remote_deploy/port"), "22")); + +	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "ssh_remote_deploy/extra_args_ssh", PROPERTY_HINT_MULTILINE_TEXT), "")); +	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "ssh_remote_deploy/extra_args_scp", PROPERTY_HINT_MULTILINE_TEXT), "")); +	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "ssh_remote_deploy/run_script", PROPERTY_HINT_MULTILINE_TEXT), run_script)); +	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "ssh_remote_deploy/cleanup_script", PROPERTY_HINT_MULTILINE_TEXT), cleanup_script));  }  void _rgba8_to_packbits_encode(int p_ch, int p_size, Vector<uint8_t> &p_source, Vector<uint8_t> &p_dest) { @@ -993,7 +1016,7 @@ Error EditorExportPlatformMacOS::_create_dmg(const String &p_dmg_path, const Str  	return OK;  } -bool EditorExportPlatformMacOS::is_shbang(const String &p_path) const { +bool EditorExportPlatformMacOS::is_shebang(const String &p_path) const {  	Ref<FileAccess> fb = FileAccess::open(p_path, FileAccess::READ);  	ERR_FAIL_COND_V_MSG(fb.is_null(), false, vformat("Can't open file: \"%s\".", p_path));  	uint16_t magic = fb->get_16(); @@ -1001,7 +1024,7 @@ bool EditorExportPlatformMacOS::is_shbang(const String &p_path) const {  }  bool EditorExportPlatformMacOS::is_executable(const String &p_path) const { -	return MachO::is_macho(p_path) || LipO::is_lipo(p_path) || is_shbang(p_path); +	return MachO::is_macho(p_path) || LipO::is_lipo(p_path) || is_shebang(p_path);  }  Error EditorExportPlatformMacOS::_export_debug_script(const Ref<EditorExportPreset> &p_preset, const String &p_app_name, const String &p_pkg_name, const String &p_path) { @@ -1082,7 +1105,6 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p  	} else {  		pkg_name = "Unnamed";  	} -  	pkg_name = OS::get_singleton()->get_safe_dir_name(pkg_name);  	String export_format; @@ -1684,7 +1706,7 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p  				zlib_filefunc_def io_dst = zipio_create_io(&io_fa_dst);  				zipFile zip = zipOpen2(p_path.utf8().get_data(), APPEND_STATUS_CREATE, nullptr, &io_dst); -				_zip_folder_recursive(zip, tmp_base_path_name, "", pkg_name); +				zip_folder_recursive(zip, tmp_base_path_name, "", pkg_name);  				zipClose(zip, nullptr);  			} @@ -1723,119 +1745,6 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p  	return err;  } -void EditorExportPlatformMacOS::_zip_folder_recursive(zipFile &p_zip, const String &p_root_path, const String &p_folder, const String &p_pkg_name) { -	String dir = p_folder.is_empty() ? p_root_path : p_root_path.path_join(p_folder); - -	Ref<DirAccess> da = DirAccess::open(dir); -	da->list_dir_begin(); -	String f = da->get_next(); -	while (!f.is_empty()) { -		if (f == "." || f == "..") { -			f = da->get_next(); -			continue; -		} -		if (da->is_link(f)) { -			OS::DateTime dt = OS::get_singleton()->get_datetime(); - -			zip_fileinfo zipfi; -			zipfi.tmz_date.tm_year = dt.year; -			zipfi.tmz_date.tm_mon = dt.month - 1; // Note: "tm" month range - 0..11, Godot month range - 1..12, https://www.cplusplus.com/reference/ctime/tm/ -			zipfi.tmz_date.tm_mday = dt.day; -			zipfi.tmz_date.tm_hour = dt.hour; -			zipfi.tmz_date.tm_min = dt.minute; -			zipfi.tmz_date.tm_sec = dt.second; -			zipfi.dosDate = 0; -			// 0120000: symbolic link type -			// 0000755: permissions rwxr-xr-x -			// 0000644: permissions rw-r--r-- -			uint32_t _mode = 0120644; -			zipfi.external_fa = (_mode << 16L) | !(_mode & 0200); -			zipfi.internal_fa = 0; - -			zipOpenNewFileInZip4(p_zip, -					p_folder.path_join(f).utf8().get_data(), -					&zipfi, -					nullptr, -					0, -					nullptr, -					0, -					nullptr, -					Z_DEFLATED, -					Z_DEFAULT_COMPRESSION, -					0, -					-MAX_WBITS, -					DEF_MEM_LEVEL, -					Z_DEFAULT_STRATEGY, -					nullptr, -					0, -					0x0314, // "version made by", 0x03 - Unix, 0x14 - ZIP specification version 2.0, required to store Unix file permissions -					0); - -			String target = da->read_link(f); -			zipWriteInFileInZip(p_zip, target.utf8().get_data(), target.utf8().size()); -			zipCloseFileInZip(p_zip); -		} else if (da->current_is_dir()) { -			_zip_folder_recursive(p_zip, p_root_path, p_folder.path_join(f), p_pkg_name); -		} else { -			OS::DateTime dt = OS::get_singleton()->get_datetime(); - -			zip_fileinfo zipfi; -			zipfi.tmz_date.tm_year = dt.year; -			zipfi.tmz_date.tm_mon = dt.month - 1; // Note: "tm" month range - 0..11, Godot month range - 1..12, https://www.cplusplus.com/reference/ctime/tm/ -			zipfi.tmz_date.tm_mday = dt.day; -			zipfi.tmz_date.tm_hour = dt.hour; -			zipfi.tmz_date.tm_min = dt.minute; -			zipfi.tmz_date.tm_sec = dt.second; -			zipfi.dosDate = 0; -			// 0100000: regular file type -			// 0000755: permissions rwxr-xr-x -			// 0000644: permissions rw-r--r-- -			uint32_t _mode = (is_executable(dir.path_join(f)) ? 0100755 : 0100644); -			zipfi.external_fa = (_mode << 16L) | !(_mode & 0200); -			zipfi.internal_fa = 0; - -			zipOpenNewFileInZip4(p_zip, -					p_folder.path_join(f).utf8().get_data(), -					&zipfi, -					nullptr, -					0, -					nullptr, -					0, -					nullptr, -					Z_DEFLATED, -					Z_DEFAULT_COMPRESSION, -					0, -					-MAX_WBITS, -					DEF_MEM_LEVEL, -					Z_DEFAULT_STRATEGY, -					nullptr, -					0, -					0x0314, // "version made by", 0x03 - Unix, 0x14 - ZIP specification version 2.0, required to store Unix file permissions -					0); - -			Ref<FileAccess> fa = FileAccess::open(dir.path_join(f), FileAccess::READ); -			if (fa.is_null()) { -				add_message(EXPORT_MESSAGE_ERROR, TTR("ZIP Creation"), vformat(TTR("Could not open file to read from path \"%s\"."), dir.path_join(f))); -				return; -			} -			const int bufsize = 16384; -			uint8_t buf[bufsize]; - -			while (true) { -				uint64_t got = fa->get_buffer(buf, bufsize); -				if (got == 0) { -					break; -				} -				zipWriteInFileInZip(p_zip, buf, got); -			} - -			zipCloseFileInZip(p_zip); -		} -		f = da->get_next(); -	} -	da->list_dir_end(); -} -  bool EditorExportPlatformMacOS::has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const {  	String err;  	bool valid = false; @@ -2012,9 +1921,242 @@ bool EditorExportPlatformMacOS::has_valid_project_configuration(const Ref<Editor  	return valid;  } -EditorExportPlatformMacOS::EditorExportPlatformMacOS() { -	logo = ImageTexture::create_from_image(memnew(Image(_macos_logo))); +Ref<Texture2D> EditorExportPlatformMacOS::get_run_icon() const { +	return run_icon; +} + +bool EditorExportPlatformMacOS::poll_export() { +	Ref<EditorExportPreset> preset; + +	for (int i = 0; i < EditorExport::get_singleton()->get_export_preset_count(); i++) { +		Ref<EditorExportPreset> ep = EditorExport::get_singleton()->get_export_preset(i); +		if (ep->is_runnable() && ep->get_platform() == this) { +			preset = ep; +			break; +		} +	} + +	int prev = menu_options; +	menu_options = (preset.is_valid() && preset->get("ssh_remote_deploy/enabled").operator bool()); +	if (ssh_pid != 0 || !cleanup_commands.is_empty()) { +		if (menu_options == 0) { +			cleanup(); +		} else { +			menu_options += 1; +		} +	} +	return menu_options != prev; +} + +Ref<ImageTexture> EditorExportPlatformMacOS::get_option_icon(int p_index) const { +	return p_index == 1 ? stop_icon : EditorExportPlatform::get_option_icon(p_index); +} + +int EditorExportPlatformMacOS::get_options_count() const { +	return menu_options; +} + +String EditorExportPlatformMacOS::get_option_label(int p_index) const { +	return (p_index) ? TTR("Stop and uninstall") : TTR("Run on remote macOS system"); +} + +String EditorExportPlatformMacOS::get_option_tooltip(int p_index) const { +	return (p_index) ? TTR("Stop and uninstall running project from the remote system") : TTR("Run exported project on remote macOS system"); +} + +void EditorExportPlatformMacOS::cleanup() { +	if (ssh_pid != 0 && OS::get_singleton()->is_process_running(ssh_pid)) { +		print_line("Terminating connection..."); +		OS::get_singleton()->kill(ssh_pid); +		OS::get_singleton()->delay_usec(1000); +	} + +	if (!cleanup_commands.is_empty()) { +		print_line("Stopping and deleting previous version..."); +		for (const SSHCleanupCommand &cmd : cleanup_commands) { +			if (cmd.wait) { +				ssh_run_on_remote(cmd.host, cmd.port, cmd.ssh_args, cmd.cmd_args); +			} else { +				ssh_run_on_remote_no_wait(cmd.host, cmd.port, cmd.ssh_args, cmd.cmd_args); +			} +		} +	} +	ssh_pid = 0; +	cleanup_commands.clear(); +} + +Error EditorExportPlatformMacOS::run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) { +	cleanup(); +	if (p_device) { // Stop command, cleanup only. +		return OK; +	} + +	EditorProgress ep("run", TTR("Running..."), 5); + +	const String dest = EditorPaths::get_singleton()->get_cache_dir().path_join("macos"); +	Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); +	if (!da->dir_exists(dest)) { +		Error err = da->make_dir_recursive(dest); +		if (err != OK) { +			EditorNode::get_singleton()->show_warning(TTR("Could not create temp directory:") + "\n" + dest); +			return err; +		} +	} + +	String pkg_name; +	if (String(ProjectSettings::get_singleton()->get("application/config/name")) != "") { +		pkg_name = String(ProjectSettings::get_singleton()->get("application/config/name")); +	} else { +		pkg_name = "Unnamed"; +	} +	pkg_name = OS::get_singleton()->get_safe_dir_name(pkg_name); + +	String host = p_preset->get("ssh_remote_deploy/host").operator String(); +	String port = p_preset->get("ssh_remote_deploy/port").operator String(); +	if (port.is_empty()) { +		port = "22"; +	} +	Vector<String> extra_args_ssh = p_preset->get("ssh_remote_deploy/extra_args_ssh").operator String().split(" "); +	Vector<String> extra_args_scp = p_preset->get("ssh_remote_deploy/extra_args_scp").operator String().split(" "); + +	const String basepath = dest.path_join("tmp_macos_export"); + +#define CLEANUP_AND_RETURN(m_err)                      \ +	{                                                  \ +		if (da->file_exists(basepath + ".zip")) {      \ +			da->remove(basepath + ".zip");             \ +		}                                              \ +		if (da->file_exists(basepath + "_start.sh")) { \ +			da->remove(basepath + "_start.sh");        \ +		}                                              \ +		if (da->file_exists(basepath + "_clean.sh")) { \ +			da->remove(basepath + "_clean.sh");        \ +		}                                              \ +		return m_err;                                  \ +	}                                                  \ +	((void)0) + +	if (ep.step(TTR("Exporting project..."), 1)) { +		return ERR_SKIP; +	} +	Error err = export_project(p_preset, true, basepath + ".zip", p_debug_flags); +	if (err != OK) { +		DirAccess::remove_file_or_error(basepath + ".zip"); +		return err; +	} + +	String cmd_args; +	{ +		Vector<String> cmd_args_list; +		gen_debug_flags(cmd_args_list, p_debug_flags); +		for (int i = 0; i < cmd_args_list.size(); i++) { +			if (i != 0) { +				cmd_args += " "; +			} +			cmd_args += cmd_args_list[i]; +		} +	} + +	const bool use_remote = (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) || (p_debug_flags & DEBUG_FLAG_DUMB_CLIENT); +	int dbg_port = EditorSettings::get_singleton()->get("network/debug/remote_port"); + +	print_line("Creating temporary directory..."); +	ep.step(TTR("Creating temporary directory..."), 2); +	String temp_dir; +	err = ssh_run_on_remote(host, port, extra_args_ssh, "mktemp -d", &temp_dir); +	if (err != OK || temp_dir.is_empty()) { +		CLEANUP_AND_RETURN(err); +	} + +	print_line("Uploading archive..."); +	ep.step(TTR("Uploading archive..."), 3); +	err = ssh_push_to_remote(host, port, extra_args_scp, basepath + ".zip", temp_dir); +	if (err != OK) { +		CLEANUP_AND_RETURN(err); +	} + +	{ +		String run_script = p_preset->get("ssh_remote_deploy/run_script"); +		run_script = run_script.replace("{temp_dir}", temp_dir); +		run_script = run_script.replace("{archive_name}", basepath.get_file() + ".zip"); +		run_script = run_script.replace("{exe_name}", pkg_name); +		run_script = run_script.replace("{cmd_args}", cmd_args); + +		Ref<FileAccess> f = FileAccess::open(basepath + "_start.sh", FileAccess::WRITE); +		if (f.is_null()) { +			CLEANUP_AND_RETURN(err); +		} + +		f->store_string(run_script); +	} + +	{ +		String clean_script = p_preset->get("ssh_remote_deploy/cleanup_script"); +		clean_script = clean_script.replace("{temp_dir}", temp_dir); +		clean_script = clean_script.replace("{archive_name}", basepath.get_file() + ".zip"); +		clean_script = clean_script.replace("{exe_name}", pkg_name); +		clean_script = clean_script.replace("{cmd_args}", cmd_args); + +		Ref<FileAccess> f = FileAccess::open(basepath + "_clean.sh", FileAccess::WRITE); +		if (f.is_null()) { +			CLEANUP_AND_RETURN(err); +		} + +		f->store_string(clean_script); +	} + +	print_line("Uploading scripts..."); +	ep.step(TTR("Uploading scripts..."), 4); +	err = ssh_push_to_remote(host, port, extra_args_scp, basepath + "_start.sh", temp_dir); +	if (err != OK) { +		CLEANUP_AND_RETURN(err); +	} +	err = ssh_run_on_remote(host, port, extra_args_ssh, vformat("chmod +x \"%s/%s\"", temp_dir, basepath.get_file() + "_start.sh")); +	if (err != OK || temp_dir.is_empty()) { +		CLEANUP_AND_RETURN(err); +	} +	err = ssh_push_to_remote(host, port, extra_args_scp, basepath + "_clean.sh", temp_dir); +	if (err != OK) { +		CLEANUP_AND_RETURN(err); +	} +	err = ssh_run_on_remote(host, port, extra_args_ssh, vformat("chmod +x \"%s/%s\"", temp_dir, basepath.get_file() + "_clean.sh")); +	if (err != OK || temp_dir.is_empty()) { +		CLEANUP_AND_RETURN(err); +	} + +	print_line("Starting project..."); +	ep.step(TTR("Starting project..."), 5); +	err = ssh_run_on_remote_no_wait(host, port, extra_args_ssh, vformat("\"%s/%s\"", temp_dir, basepath.get_file() + "_start.sh"), &ssh_pid, (use_remote) ? dbg_port : -1); +	if (err != OK) { +		CLEANUP_AND_RETURN(err); +	} + +	cleanup_commands.clear(); +	cleanup_commands.push_back(SSHCleanupCommand(host, port, extra_args_ssh, vformat("\"%s/%s\"", temp_dir, basepath.get_file() + "_clean.sh"))); + +	print_line("Project started."); + +	CLEANUP_AND_RETURN(OK); +#undef CLEANUP_AND_RETURN  } -EditorExportPlatformMacOS::~EditorExportPlatformMacOS() { +EditorExportPlatformMacOS::EditorExportPlatformMacOS() { +#ifdef MODULE_SVG_ENABLED +	Ref<Image> img = memnew(Image); +	const bool upsample = !Math::is_equal_approx(Math::round(EDSCALE), EDSCALE); + +	ImageLoaderSVG img_loader; +	img_loader.create_image_from_string(img, _macos_logo_svg, EDSCALE, upsample, false); +	logo = ImageTexture::create_from_image(img); + +	img_loader.create_image_from_string(img, _macos_run_icon_svg, EDSCALE, upsample, false); +	run_icon = ImageTexture::create_from_image(img); +#endif + +	Ref<Theme> theme = EditorNode::get_singleton()->get_editor_theme(); +	if (theme.is_valid()) { +		stop_icon = theme->get_icon(SNAME("Stop"), SNAME("EditorIcons")); +	} else { +		stop_icon.instantiate(); +	}  } diff --git a/platform/macos/export/export_plugin.h b/platform/macos/export/export_plugin.h index 7de6ddf304..c10192949c 100644 --- a/platform/macos/export/export_plugin.h +++ b/platform/macos/export/export_plugin.h @@ -36,12 +36,10 @@  #include "core/io/file_access.h"  #include "core/io/marshalls.h"  #include "core/io/resource_saver.h" -#include "core/io/zip_io.h"  #include "core/os/os.h"  #include "core/version.h"  #include "editor/editor_settings.h"  #include "editor/export/editor_export.h" -#include "platform/macos/logo.gen.h"  #include <sys/stat.h> @@ -52,6 +50,30 @@ class EditorExportPlatformMacOS : public EditorExportPlatform {  	Ref<ImageTexture> logo; +	struct SSHCleanupCommand { +		String host; +		String port; +		Vector<String> ssh_args; +		String cmd_args; +		bool wait = false; + +		SSHCleanupCommand(){}; +		SSHCleanupCommand(const String &p_host, const String &p_port, const Vector<String> &p_ssh_arg, const String &p_cmd_args, bool p_wait = false) { +			host = p_host; +			port = p_port; +			ssh_args = p_ssh_arg; +			cmd_args = p_cmd_args; +			wait = p_wait; +		}; +	}; + +	Ref<ImageTexture> run_icon; +	Ref<ImageTexture> stop_icon; + +	Vector<SSHCleanupCommand> cleanup_commands; +	OS::ProcessID ssh_pid = 0; +	int menu_options = 0; +  	void _fix_plist(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &plist, const String &p_binary);  	void _make_icon(const Ref<EditorExportPreset> &p_preset, const Ref<Image> &p_icon, Vector<uint8_t> &p_data); @@ -65,14 +87,17 @@ class EditorExportPlatformMacOS : public EditorExportPlatform {  			Ref<DirAccess> &dir_access, bool p_sign_enabled, const Ref<EditorExportPreset> &p_preset,  			const String &p_ent_path);  	Error _create_dmg(const String &p_dmg_path, const String &p_pkg_name, const String &p_app_path_name); -	void _zip_folder_recursive(zipFile &p_zip, const String &p_root_path, const String &p_folder, const String &p_pkg_name);  	Error _export_debug_script(const Ref<EditorExportPreset> &p_preset, const String &p_app_name, const String &p_pkg_name, const String &p_path);  	bool use_codesign() const { return true; }  #ifdef MACOS_ENABLED -	bool use_dmg() const { return true; } +	bool use_dmg() const { +		return true; +	}  #else -	bool use_dmg() const { return false; } +	bool use_dmg() const { +		return false; +	}  #endif  	bool is_package_name_valid(const String &p_package, String *r_error = nullptr) const { @@ -97,7 +122,7 @@ class EditorExportPlatformMacOS : public EditorExportPlatform {  		return true;  	} -	bool is_shbang(const String &p_path) const; +	bool is_shebang(const String &p_path) const;  protected:  	virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) const override; @@ -105,9 +130,15 @@ protected:  	virtual bool get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option, const HashMap<StringName, Variant> &p_options) const override;  public: -	virtual String get_name() const override { return "macOS"; } -	virtual String get_os_name() const override { return "macOS"; } -	virtual Ref<Texture2D> get_logo() const override { return logo; } +	virtual String get_name() const override { +		return "macOS"; +	} +	virtual String get_os_name() const override { +		return "macOS"; +	} +	virtual Ref<Texture2D> get_logo() const override { +		return logo; +	}  	virtual bool is_executable(const String &p_path) const override;  	virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override { @@ -133,8 +164,16 @@ public:  	virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, HashSet<String> &p_features) override {  	} +	virtual Ref<Texture2D> get_run_icon() const override; +	virtual bool poll_export() override; +	virtual Ref<ImageTexture> get_option_icon(int p_index) const override; +	virtual int get_options_count() const override; +	virtual String get_option_label(int p_index) const override; +	virtual String get_option_tooltip(int p_index) const override; +	virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) override; +	virtual void cleanup() override; +  	EditorExportPlatformMacOS(); -	~EditorExportPlatformMacOS();  };  #endif // MACOS_EXPORT_PLUGIN_H diff --git a/platform/macos/logo.png b/platform/macos/logo.png Binary files differdeleted file mode 100644 index b5a660b165..0000000000 --- a/platform/macos/logo.png +++ /dev/null diff --git a/platform/macos/logo.svg b/platform/macos/logo.svg new file mode 100644 index 0000000000..759583d76b --- /dev/null +++ b/platform/macos/logo.svg @@ -0,0 +1 @@ +<svg height="32" viewBox="0 0 25.6 25.6" width="32" xmlns="http://www.w3.org/2000/svg"><path d="M.948 19.474V6.126c0-2.767 2.42-5.178 5.178-5.178h6.904s-2.992 7.506-2.992 13.233c0 .346.337.69.69.69h2.877c0 6.648.906 8.436 1.266 9.781H6.126c-2.75 0-5.178-2.407-5.178-5.178z" fill="#1c9ef8"/><path d="M7.162 8.312v1.496" fill="none" stroke="#323232" stroke-linecap="round" stroke-width="1.036"/><path d="M10.729 14.871c-.352 0-.69-.344-.69-.69C10.038 8.414 13.03.948 13.03.948h6.444c2.77 0 5.178 2.416 5.178 5.178v13.348c0 2.754-2.407 5.178-5.178 5.178h-4.603c-.357-1.333-1.266-2.887-1.266-9.78z" fill="#e1e6ed"/><g fill="none" stroke="#323232" stroke-linecap="round"><path d="M17.575 8.255V9.75" stroke-width="1.036"/><path d="M5.55 16.943c1.037 1.794 3.907 2.876 6.79 2.876 2.877 0 5.745-1.068 6.789-2.876" stroke-width=".69"/></g></svg> diff --git a/platform/macos/run_icon.svg b/platform/macos/run_icon.svg new file mode 100644 index 0000000000..c7067bb4b6 --- /dev/null +++ b/platform/macos/run_icon.svg @@ -0,0 +1 @@ +<svg height="16" width="16" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path style="color:#000;fill:#e0e0e0;fill-opacity:1;stroke-width:.93168;-inkscape-stroke:none" d="M4.418 1.055c-1.82 0-3.365 1.537-3.365 3.363v7.164c0 1.829 1.548 3.363 3.365 3.363h7.164c1.828 0 3.363-1.544 3.363-3.363V4.418c0-1.822-1.537-3.363-3.363-3.363H7.729Zm3.875 1.164h3.291c1.149 0 2.2 1.053 2.2 2.199v7.164c0 1.14-1.052 2.2-2.2 2.2h-2.15a8.884 8.884 0 0 1-.358-1.598c-.135.02-.487.082-.693.117a3.947 3.947 0 0 1-.049.004l-.008-.002a7.345 7.345 0 0 1-1.205-.004 7.114 7.114 0 0 1-.926-.139 6.057 6.057 0 0 1-.867-.271 3.843 3.843 0 0 1-.988-.566 3.214 3.214 0 0 1-.397-.378 2.8 2.8 0 0 1-.318-.441.558.558 0 0 1-.059-.424.564.564 0 0 1 .881-.299.56.56 0 0 1 .145.164c.083.138.188.26.312.362.096.082.2.158.307.224.12.075.243.142.371.201.285.139.583.247.89.319a5.35 5.35 0 0 0 1.282.158c.065 0 .129-.005.184-.006.056 0 .102-.005.148-.008l.096-.006c.114-.009.228-.02.31-.032.083-.013.11-.021.143-.028.099-.022.204-.058.327-.089a28.438 28.438 0 0 1-.06-1.929V8.53H6.887c.048-1.963.746-4.357 1.181-5.677.1-.293.184-.527.225-.633ZM4.973 5.03h.002a.562.562 0 0 1 .558.559v.805a.556.556 0 0 1-.558.558.56.56 0 0 1-.397-.162.565.565 0 0 1-.164-.396V5.59a.561.561 0 0 1 .559-.559Z"/><path style="color:#000;fill:#e0e0e0;fill-opacity:1;stroke-linecap:round;-inkscape-stroke:none" d="M26.117 11.467c.008.11.014.225.022.328.022.283.052.565.088.846l.012.053c1.238-.252 2.448-.829 3.011-1.803a.6.6 0 1 0-1.039-.6c-.28.486-1.161.936-2.094 1.176z" transform="translate(-15.37 .357) scale(.93168)"/><g style="fill:#e0e0e0;fill-opacity:1;stroke:none;stroke-opacity:1"><path style="color:#000;fill:#e0e0e0;fill-opacity:1;stroke:none;stroke-width:1.2;stroke-linecap:round;stroke-opacity:1;-inkscape-stroke:none" d="M27.836 5.585v.862" transform="translate(-15.37 .357) scale(.93168)"/><path style="color:#000;fill:#e0e0e0;fill-opacity:1;stroke:none;stroke-linecap:round;stroke-opacity:1;-inkscape-stroke:none" d="M27.836 4.984a.6.6 0 0 0-.6.6v.863a.6.6 0 0 0 .6.6.6.6 0 0 0 .6-.6v-.863a.6.6 0 0 0-.6-.6Z" transform="translate(-15.37 .357) scale(.93168)"/></g></svg> diff --git a/platform/uwp/export/export_plugin.cpp b/platform/uwp/export/export_plugin.cpp index 6eb74fd90a..f810cb0ca9 100644 --- a/platform/uwp/export/export_plugin.cpp +++ b/platform/uwp/export/export_plugin.cpp @@ -30,8 +30,14 @@  #include "export_plugin.h" +#include "editor/editor_scale.h"  #include "editor/editor_settings.h" -#include "platform/uwp/logo.gen.h" +#include "platform/uwp/logo_svg.gen.h" + +#include "modules/modules_enabled.gen.h" // For svg and regex. +#ifdef MODULE_SVG_ENABLED +#include "modules/svg/image_loader_svg.h" +#endif  String EditorExportPlatformUWP::get_name() const {  	return "UWP"; @@ -504,5 +510,13 @@ void EditorExportPlatformUWP::resolve_platform_feature_priorities(const Ref<Edit  }  EditorExportPlatformUWP::EditorExportPlatformUWP() { -	logo = ImageTexture::create_from_image(memnew(Image(_uwp_logo))); +#ifdef MODULE_SVG_ENABLED +	Ref<Image> img = memnew(Image); +	const bool upsample = !Math::is_equal_approx(Math::round(EDSCALE), EDSCALE); + +	ImageLoaderSVG img_loader; +	img_loader.create_image_from_string(img, _uwp_logo_svg, EDSCALE, upsample, false); + +	logo = ImageTexture::create_from_image(img); +#endif  } diff --git a/platform/uwp/logo.png b/platform/uwp/logo.png Binary files differdeleted file mode 100644 index 9017a30636..0000000000 --- a/platform/uwp/logo.png +++ /dev/null diff --git a/platform/uwp/logo.svg b/platform/uwp/logo.svg new file mode 100644 index 0000000000..5bcbdcfcd4 --- /dev/null +++ b/platform/uwp/logo.svg @@ -0,0 +1 @@ +<svg height="32" width="32" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><g transform="translate(8.981 1.816)"><circle style="fill:#2f75bb;fill-opacity:1;stroke:none;stroke-width:.815427" cx="7.019" cy="14.184" r="13.825"/><path d="m-1.192 8.234 6.73-.927v6.503h-6.73Zm0 11.899 6.73.927v-6.422h-6.73Zm7.47 1.026 8.952 1.236v-7.757H6.278Zm0-13.951v6.602h8.952V5.973Z" fill="#00abed" style="fill:#fff;fill-opacity:1"/></g></svg> diff --git a/platform/web/export/export_plugin.cpp b/platform/web/export/export_plugin.cpp index 3e11db6887..9c31ca1268 100644 --- a/platform/web/export/export_plugin.cpp +++ b/platform/web/export/export_plugin.cpp @@ -31,7 +31,15 @@  #include "export_plugin.h"  #include "core/config/project_settings.h" +#include "editor/editor_scale.h"  #include "editor/editor_settings.h" +#include "platform/web/logo_svg.gen.h" +#include "platform/web/run_icon_svg.gen.h" + +#include "modules/modules_enabled.gen.h" // For svg. +#ifdef MODULE_SVG_ENABLED +#include "modules/svg/image_loader_svg.h" +#endif  Error EditorExportPlatformWeb::_extract_template(const String &p_template, const String &p_dir, const String &p_name, bool pwa) {  	Ref<FileAccess> io_fa; @@ -651,8 +659,17 @@ EditorExportPlatformWeb::EditorExportPlatformWeb() {  	server.instantiate();  	server_thread.start(_server_thread_poll, this); -	logo = ImageTexture::create_from_image(memnew(Image(_web_logo))); -	run_icon = ImageTexture::create_from_image(memnew(Image(_web_run_icon))); +#ifdef MODULE_SVG_ENABLED +	Ref<Image> img = memnew(Image); +	const bool upsample = !Math::is_equal_approx(Math::round(EDSCALE), EDSCALE); + +	ImageLoaderSVG img_loader; +	img_loader.create_image_from_string(img, _web_logo_svg, EDSCALE, upsample, false); +	logo = ImageTexture::create_from_image(img); + +	img_loader.create_image_from_string(img, _web_run_icon_svg, EDSCALE, upsample, false); +	run_icon = ImageTexture::create_from_image(img); +#endif  	Ref<Theme> theme = EditorNode::get_singleton()->get_editor_theme();  	if (theme.is_valid()) { diff --git a/platform/web/export/export_plugin.h b/platform/web/export/export_plugin.h index b85492b66f..e74c945837 100644 --- a/platform/web/export/export_plugin.h +++ b/platform/web/export/export_plugin.h @@ -38,11 +38,8 @@  #include "core/io/zip_io.h"  #include "editor/editor_node.h"  #include "editor/export/editor_export_platform.h" -#include "main/splash.gen.h" -#include "platform/web/logo.gen.h" -#include "platform/web/run_icon.gen.h" -  #include "editor_http_server.h" +#include "main/splash.gen.h"  class EditorExportPlatformWeb : public EditorExportPlatform {  	GDCLASS(EditorExportPlatformWeb, EditorExportPlatform); diff --git a/platform/web/logo.png b/platform/web/logo.png Binary files differdeleted file mode 100644 index c046d87dc4..0000000000 --- a/platform/web/logo.png +++ /dev/null diff --git a/platform/web/logo.svg b/platform/web/logo.svg new file mode 100644 index 0000000000..567b6f3c77 --- /dev/null +++ b/platform/web/logo.svg @@ -0,0 +1 @@ +<svg height="32" width="32" xmlns="http://www.w3.org/2000/svg"><path d="M7 5h18v21H7z" fill="#fff"/><path d="M3.143 1 5.48 27.504 15.967 31l10.553-3.496L28.857 1zM23.78 9.565H11.473l.275 3.308h11.759l-.911 9.937-6.556 1.808v.02h-.073l-6.61-1.828-.402-5.076h3.195l.234 2.552 3.583.97 3.595-.97.402-4.165H8.788L7.93 6.37h16.145z" fill="#eb6428"/></svg> diff --git a/platform/web/run_icon.png b/platform/web/run_icon.png Binary files differdeleted file mode 100644 index 574abb0150..0000000000 --- a/platform/web/run_icon.png +++ /dev/null diff --git a/platform/web/run_icon.svg b/platform/web/run_icon.svg new file mode 100644 index 0000000000..494f53cb90 --- /dev/null +++ b/platform/web/run_icon.svg @@ -0,0 +1 @@ +<svg height="16" width="16" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path d="M3.143 1 5.48 27.504 15.967 31l10.553-3.496L28.857 1ZM23.78 9.565H11.473l.275 3.308h11.759l-.911 9.937-6.556 1.808v.02h-.073l-6.61-1.828-.402-5.076h3.195l.234 2.552 3.583.97 3.595-.97.402-4.165H8.788L7.93 6.37h16.145Z" fill="#eb6428" style="fill:#e0e0e0;fill-opacity:1" transform="translate(.586 .586) scale(.46337)"/></svg> diff --git a/platform/windows/export/export.cpp b/platform/windows/export/export.cpp index b0e2f5a05b..4112bb84b5 100644 --- a/platform/windows/export/export.cpp +++ b/platform/windows/export/export.cpp @@ -51,7 +51,6 @@ void register_windows_exporter() {  	Ref<EditorExportPlatformWindows> platform;  	platform.instantiate(); -	platform->set_logo(ImageTexture::create_from_image(memnew(Image(_windows_logo))));  	platform->set_name("Windows Desktop");  	platform->set_os_name("Windows"); diff --git a/platform/windows/export/export_plugin.cpp b/platform/windows/export/export_plugin.cpp index 7c61a79fc8..bf32b16018 100644 --- a/platform/windows/export/export_plugin.cpp +++ b/platform/windows/export/export_plugin.cpp @@ -34,6 +34,14 @@  #include "core/io/image_loader.h"  #include "editor/editor_node.h"  #include "editor/editor_paths.h" +#include "editor/editor_scale.h" +#include "platform/windows/logo_svg.gen.h" +#include "platform/windows/run_icon_svg.gen.h" + +#include "modules/modules_enabled.gen.h" // For svg. +#ifdef MODULE_SVG_ENABLED +#include "modules/svg/image_loader_svg.h" +#endif  Error EditorExportPlatformWindows::_process_icon(const Ref<EditorExportPreset> &p_preset, const String &p_src_path, const String &p_dst_path) {  	static const uint8_t icon_size[] = { 16, 32, 48, 64, 128, 0 /*256*/ }; @@ -168,9 +176,39 @@ Error EditorExportPlatformWindows::modify_template(const Ref<EditorExportPreset>  }  Error EditorExportPlatformWindows::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { -	String pck_path = p_path; -	if (p_preset->get("binary_format/embed_pck")) { -		pck_path = p_path.get_basename() + ".tmp"; +	bool export_as_zip = p_path.ends_with("zip"); +	bool embedded = p_preset->get("binary_format/embed_pck"); + +	String pkg_name; +	if (String(ProjectSettings::get_singleton()->get("application/config/name")) != "") { +		pkg_name = String(ProjectSettings::get_singleton()->get("application/config/name")); +	} else { +		pkg_name = "Unnamed"; +	} + +	pkg_name = OS::get_singleton()->get_safe_dir_name(pkg_name); + +	// Setup temp folder. +	String path = p_path; +	String tmp_dir_path = EditorPaths::get_singleton()->get_cache_dir().path_join(pkg_name); +	Ref<DirAccess> tmp_app_dir = DirAccess::create_for_path(tmp_dir_path); +	if (export_as_zip) { +		if (tmp_app_dir.is_null()) { +			return ERR_CANT_CREATE; +		} +		if (DirAccess::exists(tmp_dir_path)) { +			if (tmp_app_dir->change_dir(tmp_dir_path) == OK) { +				tmp_app_dir->erase_contents_recursive(); +			} +		} +		tmp_app_dir->make_dir_recursive(tmp_dir_path); +		path = tmp_dir_path.path_join(p_path.get_file().get_basename() + ".exe"); +	} + +	// Export project. +	String pck_path = path; +	if (embedded) { +		pck_path = pck_path.get_basename() + ".tmp";  	}  	Error err = EditorExportPlatformPC::export_project(p_preset, p_debug, pck_path, p_flags);  	if (p_preset->get("codesign/enable") && err == OK) { @@ -181,7 +219,7 @@ Error EditorExportPlatformWindows::export_project(const Ref<EditorExportPreset>  		}  	} -	if (p_preset->get("binary_format/embed_pck") && err == OK) { +	if (embedded && err == OK) {  		Ref<DirAccess> tmp_dir = DirAccess::create_for_path(p_path.get_base_dir());  		err = tmp_dir->rename(pck_path, p_path);  		if (err != OK) { @@ -189,6 +227,27 @@ Error EditorExportPlatformWindows::export_project(const Ref<EditorExportPreset>  		}  	} +	// ZIP project. +	if (export_as_zip) { +		if (FileAccess::exists(p_path)) { +			OS::get_singleton()->move_to_trash(p_path); +		} + +		Ref<FileAccess> io_fa_dst; +		zlib_filefunc_def io_dst = zipio_create_io(&io_fa_dst); +		zipFile zip = zipOpen2(p_path.utf8().get_data(), APPEND_STATUS_CREATE, nullptr, &io_dst); + +		zip_folder_recursive(zip, tmp_dir_path, "", pkg_name); + +		zipClose(zip, nullptr); + +		if (tmp_app_dir->change_dir(tmp_dir_path) == OK) { +			tmp_app_dir->erase_contents_recursive(); +			tmp_app_dir->change_dir(".."); +			tmp_app_dir->remove(pkg_name); +		} +	} +  	return err;  } @@ -199,6 +258,7 @@ String EditorExportPlatformWindows::get_template_file_name(const String &p_targe  List<String> EditorExportPlatformWindows::get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const {  	List<String> list;  	list.push_back("exe"); +	list.push_back("zip");  	return list;  } @@ -212,6 +272,7 @@ bool EditorExportPlatformWindows::get_export_option_visibility(const EditorExpor  void EditorExportPlatformWindows::get_export_options(List<ExportOption> *r_options) {  	EditorExportPlatformPC::get_export_options(r_options); +  	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "binary_format/architecture", PROPERTY_HINT_ENUM, "x86_64,x86_32,arm64"), "x86_64"));  	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/enable"), false)); @@ -235,6 +296,29 @@ void EditorExportPlatformWindows::get_export_options(List<ExportOption> *r_optio  	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/file_description"), ""));  	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/copyright"), ""));  	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/trademarks"), "")); + +	String run_script = "Expand-Archive -LiteralPath '{temp_dir}\\{archive_name}' -DestinationPath '{temp_dir}'\n" +						"$action = New-ScheduledTaskAction -Execute '{temp_dir}\\{exe_name}' -Argument '{cmd_args}'\n" +						"$trigger = New-ScheduledTaskTrigger -Once -At 00:00\n" +						"$settings = New-ScheduledTaskSettingsSet\n" +						"$task = New-ScheduledTask -Action $action -Trigger $trigger -Settings $settings\n" +						"Register-ScheduledTask godot_remote_debug -InputObject $task -Force:$true\n" +						"Start-ScheduledTask -TaskName godot_remote_debug\n" +						"while (Get-ScheduledTask -TaskName godot_remote_debug | ? State -eq running) { Start-Sleep -Milliseconds 100 }\n" +						"Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue"; + +	String cleanup_script = "Stop-ScheduledTask -TaskName godot_remote_debug -ErrorAction:SilentlyContinue\n" +							"Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue\n" +							"Remove-Item -Recurse -Force '{temp_dir}'"; + +	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "ssh_remote_deploy/enabled"), false)); +	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "ssh_remote_deploy/host"), "user@host_ip")); +	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "ssh_remote_deploy/port"), "22")); + +	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "ssh_remote_deploy/extra_args_ssh", PROPERTY_HINT_MULTILINE_TEXT), "")); +	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "ssh_remote_deploy/extra_args_scp", PROPERTY_HINT_MULTILINE_TEXT), "")); +	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "ssh_remote_deploy/run_script", PROPERTY_HINT_MULTILINE_TEXT), run_script)); +	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "ssh_remote_deploy/cleanup_script", PROPERTY_HINT_MULTILINE_TEXT), cleanup_script));  }  Error EditorExportPlatformWindows::_rcedit_add_data(const Ref<EditorExportPreset> &p_preset, const String &p_path, bool p_console_icon) { @@ -659,3 +743,227 @@ Error EditorExportPlatformWindows::fixup_embedded_pck(const String &p_path, int6  	}  	return OK;  } + +Ref<Texture2D> EditorExportPlatformWindows::get_run_icon() const { +	return run_icon; +} + +bool EditorExportPlatformWindows::poll_export() { +	Ref<EditorExportPreset> preset; + +	for (int i = 0; i < EditorExport::get_singleton()->get_export_preset_count(); i++) { +		Ref<EditorExportPreset> ep = EditorExport::get_singleton()->get_export_preset(i); +		if (ep->is_runnable() && ep->get_platform() == this) { +			preset = ep; +			break; +		} +	} + +	int prev = menu_options; +	menu_options = (preset.is_valid() && preset->get("ssh_remote_deploy/enabled").operator bool()); +	if (ssh_pid != 0 || !cleanup_commands.is_empty()) { +		if (menu_options == 0) { +			cleanup(); +		} else { +			menu_options += 1; +		} +	} +	return menu_options != prev; +} + +Ref<ImageTexture> EditorExportPlatformWindows::get_option_icon(int p_index) const { +	return p_index == 1 ? stop_icon : EditorExportPlatform::get_option_icon(p_index); +} + +int EditorExportPlatformWindows::get_options_count() const { +	return menu_options; +} + +String EditorExportPlatformWindows::get_option_label(int p_index) const { +	return (p_index) ? TTR("Stop and uninstall") : TTR("Run on remote Windows system"); +} + +String EditorExportPlatformWindows::get_option_tooltip(int p_index) const { +	return (p_index) ? TTR("Stop and uninstall running project from the remote system") : TTR("Run exported project on remote Windows system"); +} + +void EditorExportPlatformWindows::cleanup() { +	if (ssh_pid != 0 && OS::get_singleton()->is_process_running(ssh_pid)) { +		print_line("Terminating connection..."); +		OS::get_singleton()->kill(ssh_pid); +		OS::get_singleton()->delay_usec(1000); +	} + +	if (!cleanup_commands.is_empty()) { +		print_line("Stopping and deleting previous version..."); +		for (const SSHCleanupCommand &cmd : cleanup_commands) { +			if (cmd.wait) { +				ssh_run_on_remote(cmd.host, cmd.port, cmd.ssh_args, cmd.cmd_args); +			} else { +				ssh_run_on_remote_no_wait(cmd.host, cmd.port, cmd.ssh_args, cmd.cmd_args); +			} +		} +	} +	ssh_pid = 0; +	cleanup_commands.clear(); +} + +Error EditorExportPlatformWindows::run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) { +	cleanup(); +	if (p_device) { // Stop command, cleanup only. +		return OK; +	} + +	EditorProgress ep("run", TTR("Running..."), 5); + +	const String dest = EditorPaths::get_singleton()->get_cache_dir().path_join("windows"); +	Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); +	if (!da->dir_exists(dest)) { +		Error err = da->make_dir_recursive(dest); +		if (err != OK) { +			EditorNode::get_singleton()->show_warning(TTR("Could not create temp directory:") + "\n" + dest); +			return err; +		} +	} + +	String host = p_preset->get("ssh_remote_deploy/host").operator String(); +	String port = p_preset->get("ssh_remote_deploy/port").operator String(); +	if (port.is_empty()) { +		port = "22"; +	} +	Vector<String> extra_args_ssh = p_preset->get("ssh_remote_deploy/extra_args_ssh").operator String().split(" "); +	Vector<String> extra_args_scp = p_preset->get("ssh_remote_deploy/extra_args_scp").operator String().split(" "); + +	const String basepath = dest.path_join("tmp_windows_export"); + +#define CLEANUP_AND_RETURN(m_err)                       \ +	{                                                   \ +		if (da->file_exists(basepath + ".zip")) {       \ +			da->remove(basepath + ".zip");              \ +		}                                               \ +		if (da->file_exists(basepath + "_start.ps1")) { \ +			da->remove(basepath + "_start.ps1");        \ +		}                                               \ +		if (da->file_exists(basepath + "_clean.ps1")) { \ +			da->remove(basepath + "_clean.ps1");        \ +		}                                               \ +		return m_err;                                   \ +	}                                                   \ +	((void)0) + +	if (ep.step(TTR("Exporting project..."), 1)) { +		return ERR_SKIP; +	} +	Error err = export_project(p_preset, true, basepath + ".zip", p_debug_flags); +	if (err != OK) { +		DirAccess::remove_file_or_error(basepath + ".zip"); +		return err; +	} + +	String cmd_args; +	{ +		Vector<String> cmd_args_list; +		gen_debug_flags(cmd_args_list, p_debug_flags); +		for (int i = 0; i < cmd_args_list.size(); i++) { +			if (i != 0) { +				cmd_args += " "; +			} +			cmd_args += cmd_args_list[i]; +		} +	} + +	const bool use_remote = (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) || (p_debug_flags & DEBUG_FLAG_DUMB_CLIENT); +	int dbg_port = EditorSettings::get_singleton()->get("network/debug/remote_port"); + +	print_line("Creating temporary directory..."); +	ep.step(TTR("Creating temporary directory..."), 2); +	String temp_dir; +	err = ssh_run_on_remote(host, port, extra_args_ssh, "powershell -command \\\"\\$tmp = Join-Path \\$Env:Temp \\$(New-Guid); New-Item -Type Directory -Path \\$tmp | Out-Null; Write-Output \\$tmp\\\"", &temp_dir); +	if (err != OK || temp_dir.is_empty()) { +		CLEANUP_AND_RETURN(err); +	} + +	print_line("Uploading archive..."); +	ep.step(TTR("Uploading archive..."), 3); +	err = ssh_push_to_remote(host, port, extra_args_scp, basepath + ".zip", temp_dir); +	if (err != OK) { +		CLEANUP_AND_RETURN(err); +	} + +	{ +		String run_script = p_preset->get("ssh_remote_deploy/run_script"); +		run_script = run_script.replace("{temp_dir}", temp_dir); +		run_script = run_script.replace("{archive_name}", basepath.get_file() + ".zip"); +		run_script = run_script.replace("{exe_name}", basepath.get_file() + ".exe"); +		run_script = run_script.replace("{cmd_args}", cmd_args); + +		Ref<FileAccess> f = FileAccess::open(basepath + "_start.ps1", FileAccess::WRITE); +		if (f.is_null()) { +			CLEANUP_AND_RETURN(err); +		} + +		f->store_string(run_script); +	} + +	{ +		String clean_script = p_preset->get("ssh_remote_deploy/cleanup_script"); +		clean_script = clean_script.replace("{temp_dir}", temp_dir); +		clean_script = clean_script.replace("{archive_name}", basepath.get_file() + ".zip"); +		clean_script = clean_script.replace("{exe_name}", basepath.get_file() + ".exe"); +		clean_script = clean_script.replace("{cmd_args}", cmd_args); + +		Ref<FileAccess> f = FileAccess::open(basepath + "_clean.ps1", FileAccess::WRITE); +		if (f.is_null()) { +			CLEANUP_AND_RETURN(err); +		} + +		f->store_string(clean_script); +	} + +	print_line("Uploading scripts..."); +	ep.step(TTR("Uploading scripts..."), 4); +	err = ssh_push_to_remote(host, port, extra_args_scp, basepath + "_start.ps1", temp_dir); +	if (err != OK) { +		CLEANUP_AND_RETURN(err); +	} +	err = ssh_push_to_remote(host, port, extra_args_scp, basepath + "_clean.ps1", temp_dir); +	if (err != OK) { +		CLEANUP_AND_RETURN(err); +	} + +	print_line("Starting project..."); +	ep.step(TTR("Starting project..."), 5); +	err = ssh_run_on_remote_no_wait(host, port, extra_args_ssh, vformat("powershell -file \"%s\\%s\"", temp_dir, basepath.get_file() + "_start.ps1"), &ssh_pid, (use_remote) ? dbg_port : -1); +	if (err != OK) { +		CLEANUP_AND_RETURN(err); +	} + +	cleanup_commands.clear(); +	cleanup_commands.push_back(SSHCleanupCommand(host, port, extra_args_ssh, vformat("powershell -file \"%s\\%s\"", temp_dir, basepath.get_file() + "_clean.ps1"))); + +	print_line("Project started."); + +	CLEANUP_AND_RETURN(OK); +#undef CLEANUP_AND_RETURN +} + +EditorExportPlatformWindows::EditorExportPlatformWindows() { +#ifdef MODULE_SVG_ENABLED +	Ref<Image> img = memnew(Image); +	const bool upsample = !Math::is_equal_approx(Math::round(EDSCALE), EDSCALE); + +	ImageLoaderSVG img_loader; +	img_loader.create_image_from_string(img, _windows_logo_svg, EDSCALE, upsample, false); +	set_logo(ImageTexture::create_from_image(img)); + +	img_loader.create_image_from_string(img, _windows_run_icon_svg, EDSCALE, upsample, false); +	run_icon = ImageTexture::create_from_image(img); +#endif + +	Ref<Theme> theme = EditorNode::get_singleton()->get_editor_theme(); +	if (theme.is_valid()) { +		stop_icon = theme->get_icon(SNAME("Stop"), SNAME("EditorIcons")); +	} else { +		stop_icon.instantiate(); +	} +} diff --git a/platform/windows/export/export_plugin.h b/platform/windows/export/export_plugin.h index 96608728d3..fa75a17a1f 100644 --- a/platform/windows/export/export_plugin.h +++ b/platform/windows/export/export_plugin.h @@ -35,9 +35,32 @@  #include "core/os/os.h"  #include "editor/editor_settings.h"  #include "editor/export/editor_export_platform_pc.h" -#include "platform/windows/logo.gen.h"  class EditorExportPlatformWindows : public EditorExportPlatformPC { +	struct SSHCleanupCommand { +		String host; +		String port; +		Vector<String> ssh_args; +		String cmd_args; +		bool wait = false; + +		SSHCleanupCommand(){}; +		SSHCleanupCommand(const String &p_host, const String &p_port, const Vector<String> &p_ssh_arg, const String &p_cmd_args, bool p_wait = false) { +			host = p_host; +			port = p_port; +			ssh_args = p_ssh_arg; +			cmd_args = p_cmd_args; +			wait = p_wait; +		}; +	}; + +	Ref<ImageTexture> run_icon; +	Ref<ImageTexture> stop_icon; + +	Vector<SSHCleanupCommand> cleanup_commands; +	OS::ProcessID ssh_pid = 0; +	int menu_options = 0; +  	Error _process_icon(const Ref<EditorExportPreset> &p_preset, const String &p_src_path, const String &p_dst_path);  	Error _rcedit_add_data(const Ref<EditorExportPreset> &p_preset, const String &p_path, bool p_console_icon);  	Error _code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path); @@ -53,6 +76,17 @@ public:  	virtual bool get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option, const HashMap<StringName, Variant> &p_options) const override;  	virtual String get_template_file_name(const String &p_target, const String &p_arch) const override;  	virtual Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size) override; + +	virtual Ref<Texture2D> get_run_icon() const override; +	virtual bool poll_export() override; +	virtual Ref<ImageTexture> get_option_icon(int p_index) const override; +	virtual int get_options_count() const override; +	virtual String get_option_label(int p_index) const override; +	virtual String get_option_tooltip(int p_index) const override; +	virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) override; +	virtual void cleanup() override; + +	EditorExportPlatformWindows();  };  #endif // WINDOWS_EXPORT_PLUGIN_H diff --git a/platform/windows/logo.png b/platform/windows/logo.png Binary files differdeleted file mode 100644 index f06b463850..0000000000 --- a/platform/windows/logo.png +++ /dev/null diff --git a/platform/windows/logo.svg b/platform/windows/logo.svg new file mode 100644 index 0000000000..77a0b20766 --- /dev/null +++ b/platform/windows/logo.svg @@ -0,0 +1 @@ +<svg height="32" width="32" xmlns="http://www.w3.org/2000/svg"><path d="m1 5.132 12.295-1.694v11.879H1zm0 21.736 12.295 1.695V16.83H1zm13.647 1.875L31 31V16.83H14.647zm0-25.486v12.06H31V1z" fill="#00abed"/></svg> diff --git a/platform/windows/run_icon.svg b/platform/windows/run_icon.svg new file mode 100644 index 0000000000..0897276ef7 --- /dev/null +++ b/platform/windows/run_icon.svg @@ -0,0 +1 @@ +<svg height="16" width="16" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path d="m1.095 2.997 5.66-.78v5.469h-5.66zm0 10.006 5.66.78v-5.4h-5.66zm6.282.863 7.528 1.04V8.381H7.377Zm0-11.732v5.552h7.528V1.095Z" fill="#00abed" style="stroke-width:.460341;fill:#e0e0e0;fill-opacity:1"/></svg>  |