From cebefc9f5d26bc5207e6ba399e67a82f76216f13 Mon Sep 17 00:00:00 2001 From: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Sun, 8 May 2022 10:46:53 +0300 Subject: [Export] Add one-click deploy over SSH for the desktop exports. Add one-click deploy over SSH for the desktop exports. Add ZIP export option for Linux and Windows. Change export plugin icons to SVG format. --- editor/editor_property_name_processor.cpp | 1 + editor/editor_run_native.cpp | 4 +- editor/export/editor_export.cpp | 6 + editor/export/editor_export_platform.cpp | 233 +++++++++++++++++ editor/export/editor_export_platform.h | 10 +- editor/export/editor_export_platform_pc.h | 1 - editor/export/editor_export_plugin.cpp | 5 + methods.py | 36 +-- platform/android/export/export_plugin.cpp | 23 +- platform/android/logo.png | Bin 968 -> 0 bytes platform/android/logo.svg | 1 + platform/android/run_icon.png | Bin 324 -> 0 bytes platform/android/run_icon.svg | 1 + platform/ios/export/export_plugin.cpp | 17 +- platform/ios/export/export_plugin.h | 1 - platform/ios/logo.png | Bin 1297 -> 0 bytes platform/ios/logo.svg | 1 + platform/linuxbsd/export/export.cpp | 1 - platform/linuxbsd/export/export_plugin.cpp | 344 +++++++++++++++++++++++++- platform/linuxbsd/export/export_plugin.h | 45 +++- platform/linuxbsd/logo.png | Bin 1679 -> 0 bytes platform/linuxbsd/logo.svg | 1 + platform/linuxbsd/run_icon.svg | 1 + platform/macos/export/export_plugin.cpp | 384 ++++++++++++++++++++--------- platform/macos/export/export_plugin.h | 59 ++++- platform/macos/logo.png | Bin 7195 -> 0 bytes platform/macos/logo.svg | 1 + platform/macos/run_icon.svg | 1 + platform/uwp/export/export_plugin.cpp | 18 +- platform/uwp/logo.png | Bin 1519 -> 0 bytes platform/uwp/logo.svg | 1 + platform/web/export/export_plugin.cpp | 21 +- platform/web/export/export_plugin.h | 5 +- platform/web/logo.png | Bin 1234 -> 0 bytes platform/web/logo.svg | 1 + platform/web/run_icon.png | Bin 290 -> 0 bytes platform/web/run_icon.svg | 1 + platform/windows/export/export.cpp | 1 - platform/windows/export/export_plugin.cpp | 316 +++++++++++++++++++++++- platform/windows/export/export_plugin.h | 36 ++- platform/windows/logo.png | Bin 1536 -> 0 bytes platform/windows/logo.svg | 1 + platform/windows/run_icon.svg | 1 + 43 files changed, 1392 insertions(+), 187 deletions(-) delete mode 100644 platform/android/logo.png create mode 100644 platform/android/logo.svg delete mode 100644 platform/android/run_icon.png create mode 100644 platform/android/run_icon.svg delete mode 100644 platform/ios/logo.png create mode 100644 platform/ios/logo.svg delete mode 100644 platform/linuxbsd/logo.png create mode 100644 platform/linuxbsd/logo.svg create mode 100644 platform/linuxbsd/run_icon.svg delete mode 100644 platform/macos/logo.png create mode 100644 platform/macos/logo.svg create mode 100644 platform/macos/run_icon.svg delete mode 100644 platform/uwp/logo.png create mode 100644 platform/uwp/logo.svg delete mode 100644 platform/web/logo.png create mode 100644 platform/web/logo.svg delete mode 100644 platform/web/run_icon.png create mode 100644 platform/web/run_icon.svg delete mode 100644 platform/windows/logo.png create mode 100644 platform/windows/logo.svg create mode 100644 platform/windows/run_icon.svg diff --git a/editor/editor_property_name_processor.cpp b/editor/editor_property_name_processor.cpp index e8a0912d66..124a923c76 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 47a9661bcb..2fe594531c 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 c2aa27b34d..0f580f6bc1 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 d0dcbc3bfd..d9229be9fd 100644 --- a/editor/export/editor_export_platform.cpp +++ b/editor/export/editor_export_platform.cpp @@ -1320,6 +1320,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 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 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 &p_preset, bool p_debug, const String &p_path, Vector *p_so_files, bool p_embed, int64_t *r_embedded_start, int64_t *r_embedded_size) { EditorProgress ep("savepack", TTR("Packing"), 102, true); @@ -1640,5 +1755,123 @@ bool EditorExportPlatform::can_export(const Ref &p_preset, S return valid; } +Error EditorExportPlatform::ssh_run_on_remote(const String &p_host, const String &p_port, const Vector &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 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 &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 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 &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 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 5db79b98d1..c49fca0d44 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 &p_paths); void _export_find_dependencies(const String &p_path, HashSet &p_paths); - void gen_debug_flags(Vector &r_flags, int p_flags); static Error _save_pack_file(void *p_userdata, const String &p_path, const Vector &p_data, int p_file, int p_total, const Vector &p_enc_in_filters, const Vector &p_enc_ex_filters, const Vector &p_key); static Error _save_zip_file(void *p_userdata, const String &p_path, const Vector &p_data, int p_file, int p_total, const Vector &p_enc_in_filters, const Vector &p_enc_ex_filters, const Vector &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 &r_flags, int p_flags); + void gen_debug_flags(Vector &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 &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 &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 &p_scp_args, const String &p_src_file, const String &p_dst_file) const; public: virtual void get_preset_features(const Ref &p_preset, List *r_features) const = 0; @@ -215,6 +222,7 @@ public: DEBUG_FLAG_VIEW_NAVIGATION = 16, }; + virtual void cleanup() {} virtual Error run(const Ref &p_preset, int p_device, int p_debug_flags) { return OK; } virtual Ref 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 cf96db6c2d..6f0c855130 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 &p_preset, bool p_debug, const String &p_path, int p_flags) { return OK; }; virtual Error export_project_data(const Ref &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 f0e841f307..d8b1316613 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 99a59b49e3..be449dfa02 100644 --- a/methods.py +++ b/methods.py @@ -484,29 +484,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 77cfa99aee..f44d10f96d 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 @@ -3234,8 +3240,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 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 deleted file mode 100644 index 9c8be93646..0000000000 Binary files a/platform/android/logo.png and /dev/null differ 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 @@ + diff --git a/platform/android/run_icon.png b/platform/android/run_icon.png deleted file mode 100644 index b687c9ac31..0000000000 Binary files a/platform/android/run_icon.png and /dev/null differ 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 @@ + diff --git a/platform/ios/export/export_plugin.cpp b/platform/ios/export/export_plugin.cpp index ea37278309..60b4fd6336 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 &p_preset, List *r_features) const { // Vulkan and OpenGL ES 3.0 both mandate ETC2 support. @@ -1914,7 +1921,15 @@ bool EditorExportPlatformIOS::has_valid_project_configuration(const Ref 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 639f2416a5..64804b4baa 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 deleted file mode 100644 index 966d8aa70a..0000000000 Binary files a/platform/ios/logo.png and /dev/null differ 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 @@ + diff --git a/platform/linuxbsd/export/export.cpp b/platform/linuxbsd/export/export.cpp index 990351d13f..6737d777c6 100644 --- a/platform/linuxbsd/export/export.cpp +++ b/platform/linuxbsd/export/export.cpp @@ -36,7 +36,6 @@ void register_linuxbsd_exporter() { Ref 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 8277bb1505..92f37508db 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 &p_preset, const String &p_app_name, const String &p_pkg_name, const String &p_path) { Ref f = FileAccess::open(p_path, FileAccess::WRITE); @@ -49,26 +58,47 @@ Error EditorExportPlatformLinuxBSD::_export_debug_script(const Ref &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 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 } } + // ZIP project. + if (export_as_zip) { + if (FileAccess::exists(p_path)) { + OS::get_singleton()->move_to_trash(p_path); + } + + Ref 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 EditorExportPlatformLinuxBSD::get_binary_extensions(const Ref &p_preset) const { List list; list.push_back(p_preset->get("binary_format/architecture")); + list.push_back("zip"); + return list; } void EditorExportPlatformLinuxBSD::get_export_options(List *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 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 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 EditorExportPlatformLinuxBSD::get_run_icon() const { + return run_icon; +} + +bool EditorExportPlatformLinuxBSD::poll_export() { + Ref preset; + + for (int i = 0; i < EditorExport::get_singleton()->get_export_preset_count(); i++) { + Ref 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 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 &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 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 extra_args_ssh = p_preset->get("ssh_remote_deploy/extra_args_ssh").operator String().split(" "); + Vector 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 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 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 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 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 = 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 4d6737498b..bfdb70ca70 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 extensions; + + struct SSHCleanupCommand { + String host; + String port; + Vector ssh_args; + String cmd_args; + bool wait = false; + + SSHCleanupCommand(){}; + SSHCleanupCommand(const String &p_host, const String &p_port, const Vector &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 run_icon; + Ref stop_icon; + + Vector 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 &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 get_binary_extensions(const Ref &p_preset) const override; virtual void get_export_options(List *r_options) override; + virtual List get_binary_extensions(const Ref &p_preset) const override; virtual Error export_project(const Ref &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 get_run_icon() const override; + virtual bool poll_export() override; + virtual Ref 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 &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 deleted file mode 100644 index 078654b757..0000000000 Binary files a/platform/linuxbsd/logo.png and /dev/null differ 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 @@ + 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 @@ + diff --git a/platform/macos/export/export_plugin.cpp b/platform/macos/export/export_plugin.cpp index 49c8c7758d..99d077984d 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 &p_preset, List *r_features) const { if (p_preset->get("texture_format/s3tc")) { @@ -207,6 +213,23 @@ void EditorExportPlatformMacOS::get_export_options(List *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 &p_source, Vector &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 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 &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 &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 &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 &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 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 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 &p_preset, String &r_error, bool &r_missing_templates) const { String err; bool valid = false; @@ -2008,9 +1917,242 @@ bool EditorExportPlatformMacOS::has_valid_project_configuration(const Ref EditorExportPlatformMacOS::get_run_icon() const { + return run_icon; +} + +bool EditorExportPlatformMacOS::poll_export() { + Ref preset; + + for (int i = 0; i < EditorExport::get_singleton()->get_export_preset_count(); i++) { + Ref 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 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 &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 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 extra_args_ssh = p_preset->get("ssh_remote_deploy/extra_args_ssh").operator String().split(" "); + Vector 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 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 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 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 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 = 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 af7570c394..c4529d1af8 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 @@ -52,6 +50,30 @@ class EditorExportPlatformMacOS : public EditorExportPlatform { Ref logo; + struct SSHCleanupCommand { + String host; + String port; + Vector ssh_args; + String cmd_args; + bool wait = false; + + SSHCleanupCommand(){}; + SSHCleanupCommand(const String &p_host, const String &p_port, const Vector &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 run_icon; + Ref stop_icon; + + Vector cleanup_commands; + OS::ProcessID ssh_pid = 0; + int menu_options = 0; + void _fix_plist(const Ref &p_preset, Vector &plist, const String &p_binary); void _make_icon(const Ref &p_preset, const Ref &p_icon, Vector &p_data); @@ -65,14 +87,17 @@ class EditorExportPlatformMacOS : public EditorExportPlatform { Ref &dir_access, bool p_sign_enabled, const Ref &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 &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 &p_preset, List *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 &p_options) const override; public: - virtual String get_name() const override { return "macOS"; } - virtual String get_os_name() const override { return "macOS"; } - virtual Ref 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 get_logo() const override { + return logo; + } virtual bool is_executable(const String &p_path) const override; virtual List get_binary_extensions(const Ref &p_preset) const override { @@ -133,8 +164,16 @@ public: virtual void resolve_platform_feature_priorities(const Ref &p_preset, HashSet &p_features) override { } + virtual Ref get_run_icon() const override; + virtual bool poll_export() override; + virtual Ref 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 &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 deleted file mode 100644 index b5a660b165..0000000000 Binary files a/platform/macos/logo.png and /dev/null differ 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 @@ + 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 @@ + diff --git a/platform/uwp/export/export_plugin.cpp b/platform/uwp/export/export_plugin.cpp index ab0b20762f..b7c4ed9bdb 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 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 deleted file mode 100644 index 9017a30636..0000000000 Binary files a/platform/uwp/logo.png and /dev/null differ 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 @@ + diff --git a/platform/web/export/export_plugin.cpp b/platform/web/export/export_plugin.cpp index 3087b12c40..80a6a10f83 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 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 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 = 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 f11e38df09..c5c1532286 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 deleted file mode 100644 index c046d87dc4..0000000000 Binary files a/platform/web/logo.png and /dev/null differ 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 @@ + diff --git a/platform/web/run_icon.png b/platform/web/run_icon.png deleted file mode 100644 index 574abb0150..0000000000 Binary files a/platform/web/run_icon.png and /dev/null differ 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 @@ + diff --git a/platform/windows/export/export.cpp b/platform/windows/export/export.cpp index 8f91756c02..e76d719b57 100644 --- a/platform/windows/export/export.cpp +++ b/platform/windows/export/export.cpp @@ -51,7 +51,6 @@ void register_windows_exporter() { Ref 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 89f56ffd93..2add00c297 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 &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 } Error EditorExportPlatformWindows::export_project(const Ref &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 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 } } - if (p_preset->get("binary_format/embed_pck") && err == OK) { + if (embedded && err == OK) { Ref 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 } } + // ZIP project. + if (export_as_zip) { + if (FileAccess::exists(p_path)) { + OS::get_singleton()->move_to_trash(p_path); + } + + Ref 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 EditorExportPlatformWindows::get_binary_extensions(const Ref &p_preset) const { List 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 *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 *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 &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 EditorExportPlatformWindows::get_run_icon() const { + return run_icon; +} + +bool EditorExportPlatformWindows::poll_export() { + Ref preset; + + for (int i = 0; i < EditorExport::get_singleton()->get_export_preset_count(); i++) { + Ref 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 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 &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 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 extra_args_ssh = p_preset->get("ssh_remote_deploy/extra_args_ssh").operator String().split(" "); + Vector 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 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 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 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 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 = 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 a9e6d51b9d..cae8ca6e69 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 ssh_args; + String cmd_args; + bool wait = false; + + SSHCleanupCommand(){}; + SSHCleanupCommand(const String &p_host, const String &p_port, const Vector &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 run_icon; + Ref stop_icon; + + Vector cleanup_commands; + OS::ProcessID ssh_pid = 0; + int menu_options = 0; + Error _process_icon(const Ref &p_preset, const String &p_src_path, const String &p_dst_path); Error _rcedit_add_data(const Ref &p_preset, const String &p_path, bool p_console_icon); Error _code_sign(const Ref &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 &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 get_run_icon() const override; + virtual bool poll_export() override; + virtual Ref 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 &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 deleted file mode 100644 index f06b463850..0000000000 Binary files a/platform/windows/logo.png and /dev/null differ 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 @@ + 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 @@ + -- cgit v1.2.3