1 | /**************************************************************************/ |
2 | /* export_plugin.cpp */ |
3 | /**************************************************************************/ |
4 | /* This file is part of: */ |
5 | /* GODOT ENGINE */ |
6 | /* https://godotengine.org */ |
7 | /**************************************************************************/ |
8 | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ |
9 | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ |
10 | /* */ |
11 | /* Permission is hereby granted, free of charge, to any person obtaining */ |
12 | /* a copy of this software and associated documentation files (the */ |
13 | /* "Software"), to deal in the Software without restriction, including */ |
14 | /* without limitation the rights to use, copy, modify, merge, publish, */ |
15 | /* distribute, sublicense, and/or sell copies of the Software, and to */ |
16 | /* permit persons to whom the Software is furnished to do so, subject to */ |
17 | /* the following conditions: */ |
18 | /* */ |
19 | /* The above copyright notice and this permission notice shall be */ |
20 | /* included in all copies or substantial portions of the Software. */ |
21 | /* */ |
22 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ |
23 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ |
24 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ |
25 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ |
26 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ |
27 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ |
28 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ |
29 | /**************************************************************************/ |
30 | |
31 | #include "export_plugin.h" |
32 | |
33 | #include "logo_svg.gen.h" |
34 | #include "run_icon_svg.gen.h" |
35 | |
36 | #include "core/config/project_settings.h" |
37 | #include "editor/editor_node.h" |
38 | #include "editor/editor_paths.h" |
39 | #include "editor/editor_scale.h" |
40 | #include "editor/editor_string_names.h" |
41 | #include "editor/export/editor_export.h" |
42 | |
43 | #include "modules/modules_enabled.gen.h" // For svg. |
44 | #ifdef MODULE_SVG_ENABLED |
45 | #include "modules/svg/image_loader_svg.h" |
46 | #endif |
47 | |
48 | Error EditorExportPlatformLinuxBSD::_export_debug_script(const Ref<EditorExportPreset> &p_preset, const String &p_app_name, const String &p_pkg_name, const String &p_path) { |
49 | Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::WRITE); |
50 | if (f.is_null()) { |
51 | add_message(EXPORT_MESSAGE_ERROR, TTR("Debug Script Export" ), vformat(TTR("Could not open file \"%s\"." ), p_path)); |
52 | return ERR_CANT_CREATE; |
53 | } |
54 | |
55 | f->store_line("#!/bin/sh" ); |
56 | f->store_line("echo -ne '\\033c\\033]0;" + p_app_name + "\\a'" ); |
57 | f->store_line("base_path=\"$(dirname \"$(realpath \"$0\")\")\"" ); |
58 | f->store_line("\"$base_path/" + p_pkg_name + "\" \"$@\"" ); |
59 | |
60 | return OK; |
61 | } |
62 | |
63 | Error EditorExportPlatformLinuxBSD::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { |
64 | bool export_as_zip = p_path.ends_with("zip" ); |
65 | |
66 | String pkg_name; |
67 | if (String(GLOBAL_GET("application/config/name" )) != "" ) { |
68 | pkg_name = String(GLOBAL_GET("application/config/name" )); |
69 | } else { |
70 | pkg_name = "Unnamed" ; |
71 | } |
72 | |
73 | pkg_name = OS::get_singleton()->get_safe_dir_name(pkg_name); |
74 | |
75 | // Setup temp folder. |
76 | String path = p_path; |
77 | String tmp_dir_path = EditorPaths::get_singleton()->get_cache_dir().path_join(pkg_name); |
78 | |
79 | Ref<DirAccess> tmp_app_dir = DirAccess::create_for_path(tmp_dir_path); |
80 | if (export_as_zip) { |
81 | if (tmp_app_dir.is_null()) { |
82 | return ERR_CANT_CREATE; |
83 | } |
84 | if (DirAccess::exists(tmp_dir_path)) { |
85 | if (tmp_app_dir->change_dir(tmp_dir_path) == OK) { |
86 | tmp_app_dir->erase_contents_recursive(); |
87 | } |
88 | } |
89 | tmp_app_dir->make_dir_recursive(tmp_dir_path); |
90 | path = tmp_dir_path.path_join(p_path.get_file().get_basename()); |
91 | } |
92 | |
93 | // Export project. |
94 | Error err = EditorExportPlatformPC::export_project(p_preset, p_debug, path, p_flags); |
95 | if (err != OK) { |
96 | return err; |
97 | } |
98 | |
99 | // Save console wrapper. |
100 | if (err == OK) { |
101 | int con_scr = p_preset->get("debug/export_console_wrapper" ); |
102 | if ((con_scr == 1 && p_debug) || (con_scr == 2)) { |
103 | String scr_path = path.get_basename() + ".sh" ; |
104 | err = _export_debug_script(p_preset, pkg_name, path.get_file(), scr_path); |
105 | FileAccess::set_unix_permissions(scr_path, 0755); |
106 | if (err != OK) { |
107 | add_message(EXPORT_MESSAGE_ERROR, TTR("Debug Console Export" ), TTR("Could not create console wrapper." )); |
108 | } |
109 | } |
110 | } |
111 | |
112 | // ZIP project. |
113 | if (export_as_zip) { |
114 | if (FileAccess::exists(p_path)) { |
115 | OS::get_singleton()->move_to_trash(p_path); |
116 | } |
117 | |
118 | Ref<FileAccess> io_fa_dst; |
119 | zlib_filefunc_def io_dst = zipio_create_io(&io_fa_dst); |
120 | zipFile zip = zipOpen2(p_path.utf8().get_data(), APPEND_STATUS_CREATE, nullptr, &io_dst); |
121 | |
122 | zip_folder_recursive(zip, tmp_dir_path, "" , pkg_name); |
123 | |
124 | zipClose(zip, nullptr); |
125 | |
126 | if (tmp_app_dir->change_dir(tmp_dir_path) == OK) { |
127 | tmp_app_dir->erase_contents_recursive(); |
128 | tmp_app_dir->change_dir(".." ); |
129 | tmp_app_dir->remove(pkg_name); |
130 | } |
131 | } |
132 | |
133 | return err; |
134 | } |
135 | |
136 | String EditorExportPlatformLinuxBSD::get_template_file_name(const String &p_target, const String &p_arch) const { |
137 | return "linux_" + p_target + "." + p_arch; |
138 | } |
139 | |
140 | List<String> EditorExportPlatformLinuxBSD::get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const { |
141 | List<String> list; |
142 | list.push_back(p_preset->get("binary_format/architecture" )); |
143 | list.push_back("zip" ); |
144 | |
145 | return list; |
146 | } |
147 | |
148 | bool EditorExportPlatformLinuxBSD::get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option) const { |
149 | if (p_preset) { |
150 | // Hide SSH options. |
151 | bool ssh = p_preset->get("ssh_remote_deploy/enabled" ); |
152 | if (!ssh && p_option != "ssh_remote_deploy/enabled" && p_option.begins_with("ssh_remote_deploy/" )) { |
153 | return false; |
154 | } |
155 | } |
156 | return true; |
157 | } |
158 | |
159 | void EditorExportPlatformLinuxBSD::get_export_options(List<ExportOption> *r_options) const { |
160 | EditorExportPlatformPC::get_export_options(r_options); |
161 | |
162 | 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" )); |
163 | |
164 | String run_script = "#!/usr/bin/env bash\n" |
165 | "export DISPLAY=:0\n" |
166 | "unzip -o -q \"{temp_dir}/{archive_name}\" -d \"{temp_dir}\"\n" |
167 | "\"{temp_dir}/{exe_name}\" {cmd_args}" ; |
168 | |
169 | String cleanup_script = "#!/usr/bin/env bash\n" |
170 | "kill $(pgrep -x -f \"{temp_dir}/{exe_name} {cmd_args}\")\n" |
171 | "rm -rf \"{temp_dir}\"" ; |
172 | |
173 | r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "ssh_remote_deploy/enabled" ), false, true)); |
174 | r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "ssh_remote_deploy/host" ), "user@host_ip" )); |
175 | r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "ssh_remote_deploy/port" ), "22" )); |
176 | |
177 | r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "ssh_remote_deploy/extra_args_ssh" , PROPERTY_HINT_MULTILINE_TEXT), "" )); |
178 | r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "ssh_remote_deploy/extra_args_scp" , PROPERTY_HINT_MULTILINE_TEXT), "" )); |
179 | r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "ssh_remote_deploy/run_script" , PROPERTY_HINT_MULTILINE_TEXT), run_script)); |
180 | r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "ssh_remote_deploy/cleanup_script" , PROPERTY_HINT_MULTILINE_TEXT), cleanup_script)); |
181 | } |
182 | |
183 | bool EditorExportPlatformLinuxBSD::is_elf(const String &p_path) const { |
184 | Ref<FileAccess> fb = FileAccess::open(p_path, FileAccess::READ); |
185 | ERR_FAIL_COND_V_MSG(fb.is_null(), false, vformat("Can't open file: \"%s\"." , p_path)); |
186 | uint32_t magic = fb->get_32(); |
187 | return (magic == 0x464c457f); |
188 | } |
189 | |
190 | bool EditorExportPlatformLinuxBSD::is_shebang(const String &p_path) const { |
191 | Ref<FileAccess> fb = FileAccess::open(p_path, FileAccess::READ); |
192 | ERR_FAIL_COND_V_MSG(fb.is_null(), false, vformat("Can't open file: \"%s\"." , p_path)); |
193 | uint16_t magic = fb->get_16(); |
194 | return (magic == 0x2123); |
195 | } |
196 | |
197 | bool EditorExportPlatformLinuxBSD::is_executable(const String &p_path) const { |
198 | return is_elf(p_path) || is_shebang(p_path); |
199 | } |
200 | |
201 | Error EditorExportPlatformLinuxBSD::fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size) { |
202 | // Patch the header of the "pck" section in the ELF file so that it corresponds to the embedded data |
203 | |
204 | Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ_WRITE); |
205 | if (f.is_null()) { |
206 | add_message(EXPORT_MESSAGE_ERROR, TTR("PCK Embedding" ), vformat(TTR("Failed to open executable file \"%s\"." ), p_path)); |
207 | return ERR_CANT_OPEN; |
208 | } |
209 | |
210 | // Read and check ELF magic number |
211 | { |
212 | uint32_t magic = f->get_32(); |
213 | if (magic != 0x464c457f) { // 0x7F + "ELF" |
214 | add_message(EXPORT_MESSAGE_ERROR, TTR("PCK Embedding" ), TTR("Executable file header corrupted." )); |
215 | return ERR_FILE_CORRUPT; |
216 | } |
217 | } |
218 | |
219 | // Read program architecture bits from class field |
220 | |
221 | int bits = f->get_8() * 32; |
222 | |
223 | if (bits == 32 && p_embedded_size >= 0x100000000) { |
224 | add_message(EXPORT_MESSAGE_ERROR, TTR("PCK Embedding" ), TTR("32-bit executables cannot have embedded data >= 4 GiB." )); |
225 | } |
226 | |
227 | // Get info about the section header table |
228 | |
229 | int64_t section_table_pos; |
230 | int64_t ; |
231 | if (bits == 32) { |
232 | section_header_size = 40; |
233 | f->seek(0x20); |
234 | section_table_pos = f->get_32(); |
235 | f->seek(0x30); |
236 | } else { // 64 |
237 | section_header_size = 64; |
238 | f->seek(0x28); |
239 | section_table_pos = f->get_64(); |
240 | f->seek(0x3c); |
241 | } |
242 | int num_sections = f->get_16(); |
243 | int string_section_idx = f->get_16(); |
244 | |
245 | // Load the strings table |
246 | uint8_t *strings; |
247 | { |
248 | // Jump to the strings section header |
249 | f->seek(section_table_pos + string_section_idx * section_header_size); |
250 | |
251 | // Read strings data size and offset |
252 | int64_t string_data_pos; |
253 | int64_t string_data_size; |
254 | if (bits == 32) { |
255 | f->seek(f->get_position() + 0x10); |
256 | string_data_pos = f->get_32(); |
257 | string_data_size = f->get_32(); |
258 | } else { // 64 |
259 | f->seek(f->get_position() + 0x18); |
260 | string_data_pos = f->get_64(); |
261 | string_data_size = f->get_64(); |
262 | } |
263 | |
264 | // Read strings data |
265 | f->seek(string_data_pos); |
266 | strings = (uint8_t *)memalloc(string_data_size); |
267 | if (!strings) { |
268 | return ERR_OUT_OF_MEMORY; |
269 | } |
270 | f->get_buffer(strings, string_data_size); |
271 | } |
272 | |
273 | // Search for the "pck" section |
274 | |
275 | bool found = false; |
276 | for (int i = 0; i < num_sections; ++i) { |
277 | int64_t = section_table_pos + i * section_header_size; |
278 | f->seek(section_header_pos); |
279 | |
280 | uint32_t name_offset = f->get_32(); |
281 | if (strcmp((char *)strings + name_offset, "pck" ) == 0) { |
282 | // "pck" section found, let's patch! |
283 | |
284 | if (bits == 32) { |
285 | f->seek(section_header_pos + 0x10); |
286 | f->store_32(p_embedded_start); |
287 | f->store_32(p_embedded_size); |
288 | } else { // 64 |
289 | f->seek(section_header_pos + 0x18); |
290 | f->store_64(p_embedded_start); |
291 | f->store_64(p_embedded_size); |
292 | } |
293 | |
294 | found = true; |
295 | break; |
296 | } |
297 | } |
298 | |
299 | memfree(strings); |
300 | |
301 | if (!found) { |
302 | add_message(EXPORT_MESSAGE_ERROR, TTR("PCK Embedding" ), TTR("Executable \"pck\" section not found." )); |
303 | return ERR_FILE_CORRUPT; |
304 | } |
305 | return OK; |
306 | } |
307 | |
308 | Ref<Texture2D> EditorExportPlatformLinuxBSD::get_run_icon() const { |
309 | return run_icon; |
310 | } |
311 | |
312 | bool EditorExportPlatformLinuxBSD::poll_export() { |
313 | Ref<EditorExportPreset> preset; |
314 | |
315 | for (int i = 0; i < EditorExport::get_singleton()->get_export_preset_count(); i++) { |
316 | Ref<EditorExportPreset> ep = EditorExport::get_singleton()->get_export_preset(i); |
317 | if (ep->is_runnable() && ep->get_platform() == this) { |
318 | preset = ep; |
319 | break; |
320 | } |
321 | } |
322 | |
323 | int prev = menu_options; |
324 | menu_options = (preset.is_valid() && preset->get("ssh_remote_deploy/enabled" ).operator bool()); |
325 | if (ssh_pid != 0 || !cleanup_commands.is_empty()) { |
326 | if (menu_options == 0) { |
327 | cleanup(); |
328 | } else { |
329 | menu_options += 1; |
330 | } |
331 | } |
332 | return menu_options != prev; |
333 | } |
334 | |
335 | Ref<ImageTexture> EditorExportPlatformLinuxBSD::get_option_icon(int p_index) const { |
336 | return p_index == 1 ? stop_icon : EditorExportPlatform::get_option_icon(p_index); |
337 | } |
338 | |
339 | int EditorExportPlatformLinuxBSD::get_options_count() const { |
340 | return menu_options; |
341 | } |
342 | |
343 | String EditorExportPlatformLinuxBSD::get_option_label(int p_index) const { |
344 | return (p_index) ? TTR("Stop and uninstall" ) : TTR("Run on remote Linux/BSD system" ); |
345 | } |
346 | |
347 | String EditorExportPlatformLinuxBSD::get_option_tooltip(int p_index) const { |
348 | return (p_index) ? TTR("Stop and uninstall running project from the remote system" ) : TTR("Run exported project on remote Linux/BSD system" ); |
349 | } |
350 | |
351 | void EditorExportPlatformLinuxBSD::cleanup() { |
352 | if (ssh_pid != 0 && OS::get_singleton()->is_process_running(ssh_pid)) { |
353 | print_line("Terminating connection..." ); |
354 | OS::get_singleton()->kill(ssh_pid); |
355 | OS::get_singleton()->delay_usec(1000); |
356 | } |
357 | |
358 | if (!cleanup_commands.is_empty()) { |
359 | print_line("Stopping and deleting previous version..." ); |
360 | for (const SSHCleanupCommand &cmd : cleanup_commands) { |
361 | if (cmd.wait) { |
362 | ssh_run_on_remote(cmd.host, cmd.port, cmd.ssh_args, cmd.cmd_args); |
363 | } else { |
364 | ssh_run_on_remote_no_wait(cmd.host, cmd.port, cmd.ssh_args, cmd.cmd_args); |
365 | } |
366 | } |
367 | } |
368 | ssh_pid = 0; |
369 | cleanup_commands.clear(); |
370 | } |
371 | |
372 | Error EditorExportPlatformLinuxBSD::run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) { |
373 | cleanup(); |
374 | if (p_device) { // Stop command, cleanup only. |
375 | return OK; |
376 | } |
377 | |
378 | EditorProgress ep("run" , TTR("Running..." ), 5); |
379 | |
380 | const String dest = EditorPaths::get_singleton()->get_cache_dir().path_join("linuxbsd" ); |
381 | Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); |
382 | if (!da->dir_exists(dest)) { |
383 | Error err = da->make_dir_recursive(dest); |
384 | if (err != OK) { |
385 | EditorNode::get_singleton()->show_warning(TTR("Could not create temp directory:" ) + "\n" + dest); |
386 | return err; |
387 | } |
388 | } |
389 | |
390 | String host = p_preset->get("ssh_remote_deploy/host" ).operator String(); |
391 | String port = p_preset->get("ssh_remote_deploy/port" ).operator String(); |
392 | if (port.is_empty()) { |
393 | port = "22" ; |
394 | } |
395 | Vector<String> = p_preset->get("ssh_remote_deploy/extra_args_ssh" ).operator String().split(" " , false); |
396 | Vector<String> = p_preset->get("ssh_remote_deploy/extra_args_scp" ).operator String().split(" " , false); |
397 | |
398 | const String basepath = dest.path_join("tmp_linuxbsd_export" ); |
399 | |
400 | #define CLEANUP_AND_RETURN(m_err) \ |
401 | { \ |
402 | if (da->file_exists(basepath + ".zip")) { \ |
403 | da->remove(basepath + ".zip"); \ |
404 | } \ |
405 | if (da->file_exists(basepath + "_start.sh")) { \ |
406 | da->remove(basepath + "_start.sh"); \ |
407 | } \ |
408 | if (da->file_exists(basepath + "_clean.sh")) { \ |
409 | da->remove(basepath + "_clean.sh"); \ |
410 | } \ |
411 | return m_err; \ |
412 | } \ |
413 | ((void)0) |
414 | |
415 | if (ep.step(TTR("Exporting project..." ), 1)) { |
416 | return ERR_SKIP; |
417 | } |
418 | Error err = export_project(p_preset, true, basepath + ".zip" , p_debug_flags); |
419 | if (err != OK) { |
420 | DirAccess::remove_file_or_error(basepath + ".zip" ); |
421 | return err; |
422 | } |
423 | |
424 | String cmd_args; |
425 | { |
426 | Vector<String> cmd_args_list; |
427 | gen_debug_flags(cmd_args_list, p_debug_flags); |
428 | for (int i = 0; i < cmd_args_list.size(); i++) { |
429 | if (i != 0) { |
430 | cmd_args += " " ; |
431 | } |
432 | cmd_args += cmd_args_list[i]; |
433 | } |
434 | } |
435 | |
436 | const bool use_remote = (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) || (p_debug_flags & DEBUG_FLAG_DUMB_CLIENT); |
437 | int dbg_port = EditorSettings::get_singleton()->get("network/debug/remote_port" ); |
438 | |
439 | print_line("Creating temporary directory..." ); |
440 | ep.step(TTR("Creating temporary directory..." ), 2); |
441 | String temp_dir; |
442 | err = ssh_run_on_remote(host, port, extra_args_ssh, "mktemp -d" , &temp_dir); |
443 | if (err != OK || temp_dir.is_empty()) { |
444 | CLEANUP_AND_RETURN(err); |
445 | } |
446 | |
447 | print_line("Uploading archive..." ); |
448 | ep.step(TTR("Uploading archive..." ), 3); |
449 | err = ssh_push_to_remote(host, port, extra_args_scp, basepath + ".zip" , temp_dir); |
450 | if (err != OK) { |
451 | CLEANUP_AND_RETURN(err); |
452 | } |
453 | |
454 | { |
455 | String run_script = p_preset->get("ssh_remote_deploy/run_script" ); |
456 | run_script = run_script.replace("{temp_dir}" , temp_dir); |
457 | run_script = run_script.replace("{archive_name}" , basepath.get_file() + ".zip" ); |
458 | run_script = run_script.replace("{exe_name}" , basepath.get_file()); |
459 | run_script = run_script.replace("{cmd_args}" , cmd_args); |
460 | |
461 | Ref<FileAccess> f = FileAccess::open(basepath + "_start.sh" , FileAccess::WRITE); |
462 | if (f.is_null()) { |
463 | CLEANUP_AND_RETURN(err); |
464 | } |
465 | |
466 | f->store_string(run_script); |
467 | } |
468 | |
469 | { |
470 | String clean_script = p_preset->get("ssh_remote_deploy/cleanup_script" ); |
471 | clean_script = clean_script.replace("{temp_dir}" , temp_dir); |
472 | clean_script = clean_script.replace("{archive_name}" , basepath.get_file() + ".zip" ); |
473 | clean_script = clean_script.replace("{exe_name}" , basepath.get_file()); |
474 | clean_script = clean_script.replace("{cmd_args}" , cmd_args); |
475 | |
476 | Ref<FileAccess> f = FileAccess::open(basepath + "_clean.sh" , FileAccess::WRITE); |
477 | if (f.is_null()) { |
478 | CLEANUP_AND_RETURN(err); |
479 | } |
480 | |
481 | f->store_string(clean_script); |
482 | } |
483 | |
484 | print_line("Uploading scripts..." ); |
485 | ep.step(TTR("Uploading scripts..." ), 4); |
486 | err = ssh_push_to_remote(host, port, extra_args_scp, basepath + "_start.sh" , temp_dir); |
487 | if (err != OK) { |
488 | CLEANUP_AND_RETURN(err); |
489 | } |
490 | err = ssh_run_on_remote(host, port, extra_args_ssh, vformat("chmod +x \"%s/%s\"" , temp_dir, basepath.get_file() + "_start.sh" )); |
491 | if (err != OK || temp_dir.is_empty()) { |
492 | CLEANUP_AND_RETURN(err); |
493 | } |
494 | err = ssh_push_to_remote(host, port, extra_args_scp, basepath + "_clean.sh" , temp_dir); |
495 | if (err != OK) { |
496 | CLEANUP_AND_RETURN(err); |
497 | } |
498 | err = ssh_run_on_remote(host, port, extra_args_ssh, vformat("chmod +x \"%s/%s\"" , temp_dir, basepath.get_file() + "_clean.sh" )); |
499 | if (err != OK || temp_dir.is_empty()) { |
500 | CLEANUP_AND_RETURN(err); |
501 | } |
502 | |
503 | print_line("Starting project..." ); |
504 | ep.step(TTR("Starting project..." ), 5); |
505 | 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); |
506 | if (err != OK) { |
507 | CLEANUP_AND_RETURN(err); |
508 | } |
509 | |
510 | cleanup_commands.clear(); |
511 | cleanup_commands.push_back(SSHCleanupCommand(host, port, extra_args_ssh, vformat("\"%s/%s\"" , temp_dir, basepath.get_file() + "_clean.sh" ))); |
512 | |
513 | print_line("Project started." ); |
514 | |
515 | CLEANUP_AND_RETURN(OK); |
516 | #undef CLEANUP_AND_RETURN |
517 | } |
518 | |
519 | EditorExportPlatformLinuxBSD::EditorExportPlatformLinuxBSD() { |
520 | if (EditorNode::get_singleton()) { |
521 | #ifdef MODULE_SVG_ENABLED |
522 | Ref<Image> img = memnew(Image); |
523 | const bool upsample = !Math::is_equal_approx(Math::round(EDSCALE), EDSCALE); |
524 | |
525 | ImageLoaderSVG::create_image_from_string(img, _linuxbsd_logo_svg, EDSCALE, upsample, false); |
526 | set_logo(ImageTexture::create_from_image(img)); |
527 | |
528 | ImageLoaderSVG::create_image_from_string(img, _linuxbsd_run_icon_svg, EDSCALE, upsample, false); |
529 | run_icon = ImageTexture::create_from_image(img); |
530 | #endif |
531 | |
532 | Ref<Theme> theme = EditorNode::get_singleton()->get_editor_theme(); |
533 | if (theme.is_valid()) { |
534 | stop_icon = theme->get_icon(SNAME("Stop" ), EditorStringName(EditorIcons)); |
535 | } else { |
536 | stop_icon.instantiate(); |
537 | } |
538 | } |
539 | } |
540 | |