diff --git a/CHANGELOG.md b/CHANGELOG.md index f026a47..7850e6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/lihop/godot-xterm/compare/v2.0.0...HEAD) ### Added +- Automatic download of missing gdnative libraries from GitHub releases using the version defined in `addons/godot_xterm/plugin.cfg`. - `--docker` option to `build.sh` script, in order to build Linux binaries inside a docker container with an older GLIBC version. ### Changed diff --git a/addons/godot_xterm/plugin.gd b/addons/godot_xterm/plugin.gd index 608e0e7..e42e96f 100644 --- a/addons/godot_xterm/plugin.gd +++ b/addons/godot_xterm/plugin.gd @@ -1,13 +1,95 @@ tool extends EditorPlugin +const LIBS := [ + "javascript.32.wasm", + "linux.32.so", + "linux.64.so", + "osx.64.dylib", + "windows.32.dll", + "windows.64.dll" +] +const LIB_DIR := "res://addons/godot_xterm/native/bin" +const ZIP_FILE := "%s/libgodot-xterm-release.zip" % LIB_DIR + +const Unzipper := preload("./util/unzipper.gd") + +signal _native_libs_checked + var pty_supported := OS.get_name() in ["X11", "Server", "OSX"] var asciicast_import_plugin var xrdb_import_plugin var terminal_panel: Control +# Downloads release builds of native libraries from GitHub if available, otherwise +# you will need to compile them yourself. +func _download_native_libs(): + var dir := Directory.new() + var skip := true + + for lib in LIBS: + if not dir.file_exists("%s/libgodot-xterm.%s" % [LIB_DIR, lib]): + print("GodotXterm: Downloading native library libgodot-xterm.%s..." % lib) + skip = false + + if skip: + emit_signal("_native_libs_checked") + return + + var config_file := ConfigFile.new() + config_file.load("res://addons/godot_xterm/plugin.cfg") + + var http_request := HTTPRequest.new() + add_child(http_request) + http_request.connect("request_completed", self, "_on_http_request_completed", [http_request]) + http_request.download_file = ZIP_FILE + http_request.request( + ( + "https://github.com/lihop/godot-xterm/releases/download/v%s/libgodot-xterm-release.zip" + % config_file.get_value("plugin", "version") + ) + ) + + +func _on_http_request_completed( + result: int, + response_code: int, + _headers: PoolStringArray, + _body: PoolByteArray, + http_request: HTTPRequest +): + http_request.queue_free() + var dir := Directory.new() + if result == OK and response_code == 200: + var unzipped := Unzipper.unzip(ZIP_FILE) + if unzipped.error != OK: + push_error("GodotXterm: Error unzipping file: %s" % ZIP_FILE) + else: + for path in unzipped.files: + var target := "%s/%s" % [LIB_DIR, path.get_file()] + if not dir.file_exists("%s/%s" % [LIB_DIR, path.get_file()]): + if dir.copy(path, target) == OK: + print("GodotXterm: Native library %s installed!" % path.get_file()) + else: + push_error( + "GodotXterm: Error installing native library %s" % path.get_file() + ) + dir.remove(path) + dir.remove("%s/libgodot-xterm-release" % LIB_DIR) + else: + push_warning( + "GodotXterm: Error downloading native libraries. Please compile them yourself." + ) + dir.remove(ZIP_FILE) + push_warning("GodotXterm: Installed missing native libraries. A Godot restart may be required.") + emit_signal("_native_libs_checked") + + func _enter_tree(): + call_deferred("_download_native_libs") + yield(self, "_native_libs_checked") + asciicast_import_plugin = preload("./import_plugins/asciicast_import_plugin.gd").new() add_import_plugin(asciicast_import_plugin) diff --git a/addons/godot_xterm/util/unzipper.gd b/addons/godot_xterm/util/unzipper.gd new file mode 100644 index 0000000..b8c9c32 --- /dev/null +++ b/addons/godot_xterm/util/unzipper.gd @@ -0,0 +1,92 @@ +# SPDX-FileCopyrightText: 2017 AltspaceVR +# SPDX-FileContributor: Modified by Leroy Hopson +# SPDX-License-Identifier: MIT +# Source: https://github.com/sketchfab/godot-plugin/blob/00fe32f8c9e3292a98e8977882786699d02b9924/addons/sketchfab/unzip.gd +extends SceneTree + +const ARG_PREFIX = "--zip-to-unpack " + + +# Unpack a zip archive. +# zip_path is the path of zip archive to unpack. +static func unzip(zip_path) -> Dictionary: + var file := File.new() + var err = file.open(zip_path, File.READ) + if err != OK: + return {error = err} + var out = [] + var exit_code = OS.execute( + OS.get_executable_path(), + [ + "-s", + ProjectSettings.globalize_path("res://addons/godot_xterm/util/unzipper.gd"), + "--zip-to-unpack %s" % ProjectSettings.globalize_path(zip_path), + "--no-window", + "--quit", + ], + true, + out + ) + if exit_code != 0: + return {error = FAILED, files = []} + else: + var files = [] + for line in out[0].split("\n"): + if line.begins_with("UnzippedFile:"): + files.append( + ProjectSettings.localize_path( + line.replace("UnzippedFile:", "").replace("\n", "") + ) + ) + return {error = OK, files = files} + + +func _init(): + var zip_path + for arg in OS.get_cmdline_args(): + if arg.begins_with(ARG_PREFIX): + zip_path = arg.right(ARG_PREFIX.length()) + break + + if !zip_path: + push_error("No file specified") + quit(1) + + if !ProjectSettings.load_resource_pack(zip_path): + push_error("Package file not found") + quit(1) + + var name_regex = RegEx.new() + name_regex.compile("([^/\\\\]+)\\.zip") + var base_name = name_regex.search(zip_path).get_string(1) + + var out_path = zip_path.left(zip_path.find(base_name)) + base_name + "/" + Directory.new().make_dir_recursive(out_path) + unpack_dir("res://", out_path) + quit(0) + + +func unpack_dir(src_path, out_path): + var dir = Directory.new() + dir.open(src_path) + dir.list_dir_begin(true) + + var file_name = dir.get_next() + while file_name != "": + if dir.current_is_dir(): + var new_src_path = "%s%s/" % [src_path, file_name] + var new_out_path = "%s%s/" % [out_path, file_name] + Directory.new().make_dir_recursive(new_out_path) + unpack_dir(new_src_path, new_out_path) + else: + var file_src_path = "%s%s" % [src_path, file_name] + var file_out_path = "%s%s" % [out_path, file_name] + print("UnzippedFile:%s\n" % file_out_path) + var file = File.new() + file.open(file_src_path, File.READ) + var data = file.get_buffer(file.get_len()) + file.close() + file.open(file_out_path, File.WRITE) + file.store_buffer(data) + file.close() + file_name = dir.get_next()