terminal addon and bindgen

This commit is contained in:
Matt Parker
2025-08-25 18:03:29 +10:00
parent f6c44c08fe
commit 01844228ef
60 changed files with 8961 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
uid://h0iecoac1l48

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
uid://cnwhkhvgxtf6x

View File

@@ -0,0 +1,24 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>

View File

@@ -0,0 +1,9 @@
extends SceneTree
const CSharpGDExtensionBindgen = preload("csharp_gdextension_bindgen.gd")
func _initialize():
CSharpGDExtensionBindgen.generate_gdextension_csharp_scripts.callv(OS.get_cmdline_user_args())
quit()

View File

@@ -0,0 +1 @@
uid://najinjv0m445

View File

@@ -0,0 +1,849 @@
## GDExtension to C# binding generator
##
## The C# classes generated are not attached scripts, but rather wrappers that
## forward execution to a GodotObject using dynamic calls.
##
## Use the "Project -> Tools -> Generate C# GDExtension Bindings" menu item to
## generate C# bindings from GDExtension.
@tool
extends EditorPlugin
const MENU_ITEM_NAME = "Generate C# GDExtension Bindings"
const GENERATED_NAMESPACE = "GDExtensionBindgen"
const GENERATED_SCRIPTS_FOLDER = "res://GDExtensionBindgen"
enum StringNameType {
PROPERTY_NAME,
METHOD_NAME,
SIGNAL_NAME,
}
const StringNameTypeName = {
StringNameType.PROPERTY_NAME: "PropertyName",
StringNameType.METHOD_NAME: "MethodName",
StringNameType.SIGNAL_NAME: "SignalName",
}
const PASCAL_CASE_NAME_OVERRIDES = {
"BitMap": "Bitmap",
"JSONRPC": "JsonRpc",
"Object": "GodotObject",
"OpenXRIPBinding": "OpenXRIPBinding",
"SkeletonModification2DCCDIK": "SkeletonModification2DCcdik",
"SkeletonModification2DFABRIK": "SkeletonModification2DFabrik",
"SkeletonModification3DCCDIK": "SkeletonModification3DCcdik",
"SkeletonModification3DFABRIK": "SkeletonModification3DFabrik",
"System": "System_",
"Thread": "GodotThread",
}
const PASCAL_CASE_PART_OVERRIDES = {
"AA": "AA", # Anti Aliasing
"AO": "AO", # Ambient Occlusion
"FILENAME": "FileName",
"FADEIN": "FadeIn",
"FADEOUT": "FadeOut",
"FX": "FX",
"GI": "GI", # Global Illumination
"GZIP": "GZip",
"HBOX": "HBox", # Horizontal Box
"ID": "Id",
"IO": "IO", # Input/Output
"IP": "IP", # Internet Protocol
"IV": "IV", # Initialization Vector
"MACOS": "MacOS",
"NODEPATH": "NodePath",
"SPIRV": "SpirV",
"STDIN": "StdIn",
"STDOUT": "StdOut",
"USERNAME": "UserName",
"UV": "UV",
"UV2": "UV2",
"VBOX": "VBox", # Vertical Box
"WHITESPACE": "WhiteSpace",
"WM": "WM",
"XR": "XR",
"XRAPI": "XRApi",
}
func _enter_tree():
add_tool_menu_item(MENU_ITEM_NAME, generate_gdextension_csharp_scripts)
func _exit_tree():
remove_tool_menu_item(MENU_ITEM_NAME)
static func generate_csharp_script(
cls_name: StringName,
output_dir := GENERATED_SCRIPTS_FOLDER,
name_space := GENERATED_NAMESPACE,
):
var class_is_editor_only = _is_editor_extension_class(cls_name)
var parent_class = ClassDB.get_parent_class(cls_name)
var parent_class_is_extension = _is_extension_class(parent_class)
var no_inheritance = parent_class_is_extension
var engine_class = _first_non_extension_parent(cls_name)
var regions = PackedStringArray()
# Engine object used for calling engine methods
if not parent_class_is_extension:
regions.append("// Engine object used for calling engine methods\nprotected %s _object;" % parent_class)
# Constructors
var ctor_fmt
if parent_class_is_extension:
ctor_fmt = """
public {cls_name}() : base(NativeName)
{
}
protected {cls_name}(StringName @class) : base(@class)
{
}
protected {cls_name}(Variant variant) : base(variant)
{
}
protected {cls_name}([NotNull] {engine_class} @object) : base(@object)
{
}
"""
else:
ctor_fmt = """
public {cls_name}() : this(NativeName)
{
}
protected {cls_name}(StringName @class) : this(ClassDB.Instantiate(@class))
{
}
protected {cls_name}(Variant variant) : this(({engine_class}) variant)
{
}
protected {cls_name}([NotNull] {engine_class} @object)
{
_object = @object;
}
"""
var ctor = ctor_fmt.dedent().format({
cls_name = cls_name,
engine_class = engine_class,
}).strip_edges()
regions.append(ctor)
var casts = """
public static implicit operator {engine_class}({cls_name} self) => self?._object;
public static implicit operator Variant({cls_name} self) => self?._object;
public static explicit operator {cls_name}(Variant variant) => variant.AsGodotObject() != null ? new(variant) : null;
""".dedent().format({
cls_name = cls_name,
engine_class = engine_class,
}).strip_edges()
regions.append(casts)
# ENUMS
var enums = PackedStringArray()
for enum_name in ClassDB.class_get_enum_list(cls_name, true):
enums.append(_generate_enum(cls_name, enum_name))
# INTEGER CONSTANTS
var integer_constants = PackedStringArray()
for constant_name in ClassDB.class_get_integer_constant_list(cls_name, true):
if not ClassDB.class_get_integer_constant_enum(cls_name, constant_name, true).is_empty():
continue
integer_constants.append(_generate_integer_constant(cls_name, constant_name))
# PROPERTIES
var properties = PackedStringArray()
var property_names = PackedStringArray()
for property in ClassDB.class_get_property_list(cls_name, true):
if property["usage"] & (PROPERTY_USAGE_GROUP | PROPERTY_USAGE_SUBGROUP):
continue
property_names.append(property["name"])
properties.append(_generate_property(cls_name, property))
var inherited_properties = PackedStringArray()
if not parent_class_is_extension:
for inherited_class in _get_parent_classes(cls_name):
for property in ClassDB.class_get_property_list(inherited_class, true):
if property["usage"] & (PROPERTY_USAGE_GROUP | PROPERTY_USAGE_SUBGROUP):
continue
inherited_properties.append(_generate_property(inherited_class, property))
# METHODS
var methods = PackedStringArray()
var method_names = PackedStringArray()
for method in ClassDB.class_get_method_list(cls_name, true):
if method["flags"] & (METHOD_FLAG_VIRTUAL | METHOD_FLAG_VIRTUAL_REQUIRED):
continue
if method["name"].begins_with("_"):
continue
method_names.append(method["name"])
methods.append(_generate_method(cls_name, method))
var inherited_methods = PackedStringArray()
if not parent_class_is_extension:
for inherited_class in _get_parent_classes(cls_name):
for method in ClassDB.class_get_method_list(inherited_class, true):
if method["flags"] & (METHOD_FLAG_VIRTUAL | METHOD_FLAG_VIRTUAL_REQUIRED):
continue
if method["name"].begins_with("_"):
continue
inherited_methods.append(_generate_method(inherited_class, method))
# SIGNALS
var signals = PackedStringArray()
var signal_names = PackedStringArray()
for sig in ClassDB.class_get_signal_list(cls_name, true):
signal_names.append(sig["name"])
signals.append(_generate_signal(cls_name, sig))
var inherited_signals = PackedStringArray()
if not parent_class_is_extension:
for inherited_class in _get_parent_classes(cls_name):
for method in ClassDB.class_get_signal_list(inherited_class, true):
inherited_signals.append(_generate_signal(inherited_class, method))
# StringName caches
regions.append(_generate_strings_class(cls_name, StringNameType.PROPERTY_NAME, property_names))
regions.append(_generate_strings_class(cls_name, StringNameType.METHOD_NAME, method_names))
regions.append(_generate_strings_class(cls_name, StringNameType.SIGNAL_NAME, signal_names))
regions.append("private static readonly StringName NativeName = \"{cls_name}\";".format({
cls_name = cls_name,
}))
if not enums.is_empty():
regions.append("#region Enums")
regions.append("\n\n".join(enums))
regions.append("#endregion")
if not integer_constants.is_empty():
regions.append("#region Integer Constants")
regions.append("\n\n".join(integer_constants))
regions.append("#endregion")
if not properties.is_empty():
regions.append("#region Properties")
regions.append("\n\n".join(properties))
regions.append("#endregion")
if not inherited_properties.is_empty():
regions.append("#region Inherited Properties")
regions.append("\n\n".join(inherited_properties))
regions.append("#endregion")
if not methods.is_empty():
regions.append("#region Methods")
regions.append("\n\n".join(methods))
regions.append("#endregion")
if not inherited_methods.is_empty():
regions.append("#region Inherited Methods")
regions.append("\n\n".join(inherited_methods))
regions.append("#endregion")
if not signals.is_empty():
regions.append("#region Signals")
regions.append("\n\n".join(signals))
regions.append("#endregion")
if not inherited_signals.is_empty():
regions.append("#region Inherited Signals")
regions.append("\n\n".join(inherited_signals))
regions.append("#endregion")
var code = """
// This code was automatically generated by GDExtension C# Bindgen
using System;
using System.Diagnostics.CodeAnalysis;
using Godot;
namespace {name_space};
public class {cls_name}{inheritance}
{
{regions}
}
""".dedent().format({
name_space = name_space,
cls_name = cls_name,
inheritance = " : " + parent_class if parent_class_is_extension else "",
regions = "\n\n".join(regions).indent("\t"),
}).strip_edges()
if class_is_editor_only:
code = """
#if TOOLS
{code}
#endif
""".dedent().format({
code = code,
}).strip_edges()
code += "\n"
if not DirAccess.dir_exists_absolute(output_dir):
DirAccess.make_dir_recursive_absolute(output_dir)
var new_script = FileAccess.open(output_dir.path_join(cls_name + ".cs"), FileAccess.WRITE)
new_script.store_string(code)
static func generate_gdextension_csharp_scripts(
output_dir := GENERATED_SCRIPTS_FOLDER,
name_space := GENERATED_NAMESPACE,
):
var classes = ClassDB.get_class_list()
for cls_name in classes:
if _is_extension_class(cls_name):
generate_csharp_script(cls_name, output_dir, name_space)
static func _generate_enum(cls_name: StringName, enum_name: StringName) -> String:
var common_prefix = null
for constant_name in ClassDB.class_get_enum_constants(cls_name, enum_name, true):
if common_prefix == null:
common_prefix = constant_name
else:
common_prefix = _get_common_prefix(common_prefix, constant_name)
# Handle special case where one of the constants is present in all constant
# names: remove last word from prefix.
# Example case: Node.ProcessThreadMessages and FLAG_PROCESS_THREAD_MESSAGES
if common_prefix in ClassDB.class_get_enum_constants(cls_name, enum_name, true):
common_prefix = common_prefix.rsplit("_", false, 1)[0]
var constants = PackedStringArray()
for constant_name in ClassDB.class_get_enum_constants(cls_name, enum_name, true):
constants.append("{csharp_constant_name} = {constant_value}L,".format({
csharp_constant_name = constant_name.substr(common_prefix.length()).to_pascal_case(),
constant_value = ClassDB.class_get_integer_constant(cls_name, constant_name),
}))
return """
{flags}
public enum {enum_name}{maybe_enum_suffix} : long
{
{constants}
}
""".dedent().format({
flags = "[Flags]" if ClassDB.is_class_enum_bitfield(cls_name, enum_name) else "",
enum_name = enum_name,
constants = "\n".join(constants).indent("\t"),
maybe_enum_suffix = "Enum" if _needs_enum_suffix(cls_name, enum_name) else "",
}).strip_edges()
static func _generate_integer_constant(cls_name: StringName, constant_name: StringName) -> String:
return "public const long {csharp_constant_name} = {constant_value}L;".format({
csharp_constant_name = constant_name.to_pascal_case(),
constant_value = ClassDB.class_get_integer_constant(cls_name, constant_name),
})
static func _generate_property(cls_name: StringName, property: Dictionary) -> String:
var property_name = property["name"]
var csharp_property_name = property_name.to_pascal_case()
var property_type = _get_property_type(cls_name, property)
var getset = PackedStringArray()
var getter = ClassDB.class_get_property_getter(cls_name, property_name)
if getter:
if _is_extension_class(cls_name):
getset.append("get => {get_cast}_object.Get(PropertyName.{csharp_property_name});".format({
get_cast = _property_get_cast(cls_name, property),
csharp_property_name = csharp_property_name,
}))
else:
getset.append("get => _object.{csharp_property_name};".format({
csharp_property_name = csharp_property_name,
}))
var setter = ClassDB.class_get_property_setter(cls_name, property_name)
if setter:
if _is_extension_class(cls_name):
getset.append("set => _object.Set(PropertyName.{csharp_property_name}, {set_cast}value);".format({
set_cast = _property_set_cast(property),
csharp_property_name = csharp_property_name,
}))
else:
getset.append("set => _object.{csharp_property_name} = value;".format({
csharp_property_name = csharp_property_name,
}))
return """
public {property_type} {csharp_property_name}
{
{getset}
}
""".dedent().format({
property_type = property_type,
csharp_property_name = csharp_property_name,
getset = "\n".join(getset).indent("\t"),
}).strip_edges()
static func _generate_method(cls_name: StringName, method: Dictionary) -> String:
var method_name = method["name"]
var csharp_method_name = method_name.to_pascal_case()
var return_type = _get_method_return_type(cls_name, method_name, method["return"])
var is_static = method["flags"] & METHOD_FLAG_STATIC
var arg_types = PackedStringArray()
var arg_names = PackedStringArray()
var args = PackedStringArray()
for argument in method["args"]:
var arg_type = _get_property_type(cls_name, argument)
var arg_name = "@" + argument["name"]
# hardcode type that cannot be known from reflection in GDScript
if method["name"] == "connect" and arg_name == "@flags":
arg_type = "uint"
args.append("{arg_type} {arg_name}".format({
arg_type = arg_type,
arg_name = arg_name,
}))
arg_types.append(arg_type)
if _property_is_enum(argument):
arg_names.append("(int)" + arg_name)
else:
arg_names.append(arg_name)
var implementation = PackedStringArray()
var default_args = method["default_args"]
var i = args.size() - default_args.size()
for default_value in default_args:
if default_value == null:
default_value = "default"
# handle enums
elif default_value is int and arg_types[i] != "int":
default_value = ("(%s)" % arg_types[i]) + str(default_value)
# C# requires the "f" suffix for float literals
elif default_value is float and arg_types[i] == "float":
default_value = "%sf" % default_value
# NOTE: don't move this branch below the String one, since most of the
# time when arg_types[i] == StringName, default_value is String
elif default_value is StringName or arg_types[i] == "Godot.StringName":
implementation.append('%s ??= "%s";' % [arg_names[i], default_value])
default_value = "null"
elif default_value is String:
default_value = '"%s"' % default_value
elif default_value is Array:
assert(default_value.is_empty(), "Populated Array not supported yet! " + str(default_value)) # TODO: support populated array as default value
implementation.append("%s ??= new();" % arg_names[i])
default_value = "null"
elif default_value is Dictionary:
assert(default_value.is_empty(), "Populated Dictionary not supported yet! " + str(default_value)) # TODO: support populated dictionary as default value
implementation.append("%s ??= new();" % arg_names[i])
default_value = "null"
elif (
default_value is Vector2 or default_value is Vector3 or default_value is Vector4
or default_value is Color
):
args[i] = args[i].replace(arg_types[i], arg_types[i] + "?")
var impl = "%s ??= new%s;" % [arg_names[i], default_value]
if not OS.has_feature("double"):
impl = impl.replace(",", "f,").replace(")", "f)")
implementation.append(impl)
default_value = "null"
elif (
default_value is PackedByteArray
or default_value is PackedInt32Array or default_value is PackedInt64Array
or default_value is PackedFloat32Array or default_value is PackedFloat64Array
or default_value is PackedVector2Array or default_value is PackedVector3Array or default_value is PackedVector4Array
or default_value is PackedColorArray
):
assert(default_value.is_empty(), "Populated Packed Array not supported yet! " + str(default_value))
implementation.append("%s ??= System.Array.Empty<%s>();" % [arg_names[i], arg_types[i].replace("[]", "")])
default_value = "null"
elif default_value is Transform2D:
assert(default_value == Transform2D.IDENTITY, "Only identity Transform2D is supported as default value")
args[i] = args[i].replace(arg_types[i], arg_types[i] + "?")
implementation.append("%s ??= Godot.Transform2D.Identity;" % arg_names[i])
default_value = "null"
elif default_value is Transform3D:
assert(default_value == Transform3D.IDENTITY, "Only identity Transform3D is supported as default value")
args[i] = args[i].replace(arg_types[i], arg_types[i] + "?")
implementation.append("%s ??= Godot.Transform3D.Identity;" % arg_names[i])
default_value = "null"
args[i] += " = " + str(default_value)
i += 1
if method["flags"] & METHOD_FLAG_VARARG:
args.append("params Variant[] varargs")
arg_names.append("varargs")
if _is_extension_class(cls_name):
arg_names.insert(0, "MethodName.{csharp_method_name}".format({
csharp_method_name = csharp_method_name,
}))
if is_static:
implementation.append("{maybe_return}ClassDB.ClassCallStatic(NativeName, {arg_names});".format({
arg_names = ", ".join(arg_names),
maybe_return = "return " + _property_get_cast(cls_name, method["return"]) if return_type != "void" else "",
}))
else:
implementation.append("{maybe_return}_object.Call({arg_names});".format({
arg_names = ", ".join(arg_names),
maybe_return = "return " + _property_get_cast(cls_name, method["return"]) if return_type != "void" else "",
}))
else:
if is_static:
implementation.append("{maybe_return}{engine_class}.{csharp_method_name}({arg_names});".format({
arg_names = ", ".join(arg_names),
engine_class = _first_non_extension_parent(cls_name),
csharp_method_name = csharp_method_name,
maybe_return = "return " if return_type != "void" else "",
}))
else:
implementation.append("{maybe_return}_object.{csharp_method_name}({arg_names});".format({
arg_names = ", ".join(arg_names),
csharp_method_name = csharp_method_name,
maybe_return = "return " if return_type != "void" else "",
}))
return """
public {maybe_static}{maybe_override}{return_type} {csharp_method_name}({args})
{
{implementation}
}
""".dedent().format({
args = ", ".join(args),
csharp_method_name = csharp_method_name,
implementation = "\n".join(implementation).indent("\t"),
maybe_override = "override " if csharp_method_name == "ToString" else "",
maybe_static = "static " if is_static else "",
return_type = return_type,
}).strip_edges()
static func _generate_signal(cls_name: StringName, sig: Dictionary):
var signal_name = sig["name"]
var csharp_signal_name = signal_name.to_pascal_case()
var return_type = _get_method_return_type(cls_name, signal_name, sig["return"])
var arg_types = PackedStringArray()
for argument in sig["args"]:
var arg_type = _get_property_type(cls_name, argument)
arg_types.append(arg_type)
var delegate_type
if return_type == "void":
if not arg_types.is_empty():
delegate_type = "Action<{arg_types}>".format({
arg_types = ", ".join(arg_types)
})
else:
delegate_type = "Action"
else:
arg_types.append(return_type)
delegate_type = "Func<{arg_types}>".format({
arg_types = ", ".join(arg_types)
})
return """
public event {delegate_type} {csharp_signal_name}
{
add
{
Connect(SignalName.{csharp_signal_name}, Callable.From(value));
}
remove
{
Disconnect(SignalName.{csharp_signal_name}, Callable.From(value));
}
}
""".dedent().format({
delegate_type = delegate_type,
csharp_signal_name = csharp_signal_name,
}).strip_edges()
static func _property_is_enum(property: Dictionary) -> bool:
return property["usage"] & (PROPERTY_USAGE_CLASS_IS_ENUM | PROPERTY_USAGE_CLASS_IS_BITFIELD)
static func _get_property_type(cls_name: StringName, property: Dictionary) -> String:
match property["type"]:
TYPE_NIL:
return "Variant"
TYPE_BOOL:
return "bool"
TYPE_INT:
if _property_is_enum(property):
var enum_name = property["class_name"]
if enum_name == "Error":
return "Godot.Error"
var split = enum_name.split(".")
if split.size() == 1:
return enum_name + ("Enum" if _needs_enum_suffix(cls_name, enum_name) else "")
else:
return enum_name + ("Enum" if _needs_enum_suffix(split[0], split[1]) else "")
return "int"
TYPE_FLOAT:
return "double" if OS.has_feature("double") else "float"
TYPE_STRING:
return "string"
TYPE_VECTOR2I:
return "Godot.Vector2I"
TYPE_RECT2I:
return "Godot.Rect2I"
TYPE_VECTOR3I:
return "Godot.Vector3I"
TYPE_VECTOR4I:
return "Godot.Vector4I"
TYPE_AABB:
return "Godot.Aabb"
TYPE_RID:
return "Godot.Rid"
TYPE_OBJECT:
if property["class_name"] and property["class_name"] != "Object":
return _pascal_to_pascal_case(_get_class_from_class_name(property["class_name"]))
else:
return "GodotObject"
TYPE_ARRAY:
if property["hint"] & PROPERTY_HINT_ARRAY_TYPE:
return "Godot.Collections.Array<%s>" % _get_mapped_variant_type(property["hint_string"])
else:
return "Godot.Collections.Array"
TYPE_DICTIONARY:
return "Godot.Collections.Dictionary"
TYPE_PACKED_BYTE_ARRAY:
return "byte[]"
TYPE_PACKED_INT32_ARRAY:
return "int[]"
TYPE_PACKED_INT64_ARRAY:
return "long[]"
TYPE_PACKED_FLOAT32_ARRAY:
return "float[]"
TYPE_PACKED_FLOAT64_ARRAY:
return "double[]"
TYPE_PACKED_STRING_ARRAY:
return "string[]"
TYPE_PACKED_VECTOR2_ARRAY:
return "Godot.Vector2[]"
TYPE_PACKED_VECTOR3_ARRAY:
return "Godot.Vector3[]"
TYPE_PACKED_VECTOR4_ARRAY:
return "Godot.Vector4[]"
TYPE_PACKED_COLOR_ARRAY:
return "Godot.Color[]"
var t:
return "Godot." + type_string(t)
static func _get_mapped_variant_type(variant_type_name: String) -> String:
var _type_map = {
"Variant": "Variant",
type_string(TYPE_BOOL): "bool",
type_string(TYPE_INT): "int",
type_string(TYPE_FLOAT): "double" if OS.has_feature("double") else "float",
type_string(TYPE_STRING): "string",
type_string(TYPE_STRING_NAME): "StringName",
type_string(TYPE_VECTOR2I): "Godot.Vector2I",
type_string(TYPE_RECT2I): "Godot.Rect2I",
type_string(TYPE_VECTOR3I): "Godot.Vector3I",
type_string(TYPE_VECTOR4I): "Godot.Vector4I",
type_string(TYPE_AABB): "Godot.Aabb",
type_string(TYPE_RID): "Godot.Rid",
type_string(TYPE_OBJECT): "GodotObject",
type_string(TYPE_ARRAY): "Godot.Collections.Array",
type_string(TYPE_DICTIONARY): "Godot.Collections.Dictionary",
type_string(TYPE_PACKED_BYTE_ARRAY): "byte[]",
type_string(TYPE_PACKED_INT32_ARRAY): "int[]",
type_string(TYPE_PACKED_INT64_ARRAY): "long[]",
type_string(TYPE_PACKED_FLOAT32_ARRAY): "float[]",
type_string(TYPE_PACKED_FLOAT64_ARRAY): "double[]",
type_string(TYPE_PACKED_STRING_ARRAY): "string[]",
type_string(TYPE_PACKED_VECTOR2_ARRAY): "Godot.Vector2[]",
type_string(TYPE_PACKED_VECTOR3_ARRAY): "Godot.Vector3[]",
type_string(TYPE_PACKED_VECTOR4_ARRAY): "Godot.Vector4[]",
type_string(TYPE_PACKED_COLOR_ARRAY): "Godot.Color[]",
}
return _type_map.get(variant_type_name, "Godot." + variant_type_name)
static func _property_get_cast(cls_name: StringName, property: Dictionary):
var property_type = _get_property_type(cls_name, property)
if _property_is_enum(property):
return "(%s)(int)" % property_type
else:
return "(%s)" % property_type
static func _property_set_cast(property: Dictionary):
if _property_is_enum(property):
return "(int)"
else:
return ""
static func _is_extension_class(cls_name: StringName) -> bool:
return ClassDB.class_get_api_type(cls_name) in [
ClassDB.APIType.API_EXTENSION,
ClassDB.APIType.API_EDITOR_EXTENSION,
]
static func _is_editor_extension_class(cls_name: StringName) -> bool:
return ClassDB.class_get_api_type(cls_name) == ClassDB.APIType.API_EDITOR_EXTENSION
static func _first_non_extension_parent(cls_name: StringName) -> StringName:
while _is_extension_class(cls_name):
cls_name = ClassDB.get_parent_class(cls_name)
return cls_name
static func _get_method_return_type(cls_name: StringName, method_name: StringName, method_return: Dictionary) -> String:
# hardcode type that cannot be known from reflection in GDScript
if method_name == "get_instance_id":
return "ulong"
if method_return["type"] == TYPE_NIL:
if method_return["usage"] & PROPERTY_USAGE_NIL_IS_VARIANT:
return "Variant"
else:
return "void"
else:
return _get_property_type(cls_name, method_return)
static func _get_parent_classes(cls_name: StringName) -> Array[StringName]:
var parent_classes = [] as Array[StringName]
while true:
cls_name = ClassDB.get_parent_class(cls_name)
parent_classes.append(cls_name)
if cls_name == "Object":
break
return parent_classes
static func _generate_strings_class(cls_name: StringName, string_name_type: StringNameType, string_names: PackedStringArray) -> String:
var parent_class = ClassDB.get_parent_class(cls_name)
var lines = PackedStringArray()
for name in string_names:
if string_name_type == StringNameType.METHOD_NAME and ClassDB.class_has_method(parent_class, name):
continue
if string_name_type == StringNameType.SIGNAL_NAME and ClassDB.class_has_signal(parent_class, name):
continue
lines.append("public static readonly StringName {cs_name} = \"{name}\";".format({
cs_name = name.to_pascal_case(),
name = name,
}))
return """
public {maybe_new}class {strings_class} : {parent_class}.{strings_class}
{
{lines}
}
""".dedent().format({
lines = "\n".join(lines).indent("\t"),
maybe_new = "new " if _is_extension_class(parent_class) else "",
parent_class = parent_class,
strings_class = StringNameTypeName[string_name_type],
}).strip_edges()
static func _get_common_prefix(s1: String, s2: String) -> String:
var common_length = min(s1.length(), s2.length())
for i in range(common_length):
if s1[i] != s2[i]:
return s1.substr(0, i)
return s1.substr(0, common_length)
static func _get_class_from_class_name(cls_name: String) -> String:
var classes = cls_name.split(",")
if classes.size() == 1:
return cls_name
# Handle special case where 2 or more class names are present separated
# by ",": calculate the common parent class.
# Example case: CanvasItem.material uses "CanvasItemMaterial,ShaderMaterial"
var parent_classes = _get_parent_classes(classes[0])
for i in range(1, classes.size()):
var test_cls = classes[i]
while not ClassDB.is_parent_class(test_cls, parent_classes[0]):
parent_classes.pop_front()
return parent_classes[0]
static func _needs_enum_suffix(cls_name: StringName, enum_name: String) -> bool:
var snake_case_enum_name = enum_name.to_snake_case()
if ClassDB.class_has_method(cls_name, snake_case_enum_name):
return true
if ClassDB.class_has_signal(cls_name, snake_case_enum_name):
return true
var properties = ClassDB.class_get_property_list(cls_name)
for property in properties:
if snake_case_enum_name == property["name"]:
return true
return false
# Pascal case conversion used for class names.
# Replicates the logic from `godot/modules/mono/utils/naming_utils.cpp`
static func _is_ascii_upper_case(c: String) -> bool:
return c.to_upper() == c
static func _is_ascii_lower_case(c: String) -> bool:
return c.to_lower() == c
static func _is_digit(c: String) -> bool:
return c >= "0" and c <= "9"
static func _split_pascal_case(p_identifier: String) -> PackedStringArray:
var parts := PackedStringArray()
var current_part_start := 0
var prev_was_upper := _is_ascii_upper_case(p_identifier[0])
for i in range(1, p_identifier.length()):
if prev_was_upper:
if _is_digit(p_identifier[i]) or _is_ascii_lower_case(p_identifier[i]):
if not _is_digit(p_identifier[i]):
# These conditions only apply when the separator is not a digit.
if i - current_part_start == 1:
# Upper character was only the beginning of a word.
prev_was_upper = false
continue
if i != p_identifier.length():
# If this is not the last character, the last uppercase
# character is the start of the next word.
i -= 1
if i - current_part_start > 0:
parts.append(p_identifier.substr(current_part_start, i - current_part_start))
current_part_start = i
prev_was_upper = false
else:
if _is_digit(p_identifier[i]) or _is_ascii_upper_case(p_identifier[i]):
parts.append(p_identifier.substr(current_part_start, i - current_part_start))
current_part_start = i
prev_was_upper = true
# Add the rest of the identifier as the last part.
if current_part_start != p_identifier.length():
parts.append(p_identifier.substr(current_part_start))
return parts
static func _pascal_to_pascal_case(p_identifier: String) -> String:
if p_identifier.length() == 0:
return p_identifier
if p_identifier.length() <= 2:
return p_identifier.to_upper()
if PASCAL_CASE_NAME_OVERRIDES.has(p_identifier):
return PASCAL_CASE_NAME_OVERRIDES[p_identifier]
var parts := _split_pascal_case(p_identifier)
var ret := ""
for part in parts:
if PASCAL_CASE_PART_OVERRIDES.has(part):
ret += PASCAL_CASE_PART_OVERRIDES[part]
continue
if part.length() <= 2 and _is_ascii_upper_case(part):
ret += part.to_upper()
continue
part[0] = part[0].to_upper()
for i in range(1, part.length()):
if _is_digit(part[i - 1]):
# Use uppercase after digits.
part[i] = part[i].to_upper()
else:
part[i] = part[i].to_lower()
ret += part
return ret

View File

@@ -0,0 +1 @@
uid://cy4tmwy5iyogu

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://blckign68biqs"
path="res://.godot/imported/icon.png-6749c396fd9e755f292f935bb84594b0.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/csharp_gdextension_bindgen/icon.png"
dest_files=["res://.godot/imported/icon.png-6749c396fd9e755f292f935bb84594b0.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

View File

@@ -0,0 +1,7 @@
[plugin]
name="C# GDExtension Bindgen"
description="Automatic C# bindings generator for GDExtension classes (Godot 4.4+)"
author="gilzoide"
version="0.3.1"
script="csharp_gdextension_bindgen.gd"

View File

@@ -0,0 +1,362 @@
# Godot auto generated files
*.gen.*
.import/
# Documentation generated by doxygen or from classes.xml
doc/_build/
# Javascript specific
*.bc
# CLion
cmake-build-debug
# Android specific
.gradle
local.properties
*.iml
.idea
.gradletasknamecache
project.properties
platform/android/java/app/libs/*
platform/android/java/libs/*
platform/android/java/lib/.cxx/
# General c++ generated files
*.lib
*.o
*.ox
*.a
*.ax
*.d
*.so
*.os
*.Plo
*.lo
# Libs generated files
.deps/*
.dirstamp
# Gprof output
gmon.out
# Vim temp files
*.swo
*.swp
# Qt project files
*.config
*.creator
*.creator.*
*.files
*.includes
*.cflags
*.cxxflags
# Code::Blocks files
*.cbp
*.layout
*.depend
# Eclipse CDT files
.cproject
.settings/
*.pydevproject
*.launch
# Geany/geany-plugins files
*.geany
.geanyprj
# Jetbrains IDEs
.idea/
# Misc
.DS_Store
__MACOSX
logs/
# for projects that use SCons for building: http://http://www.scons.org/
.sconf_temp
.sconsign*.dblite
*.pyc
# https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.sln.docstates
*.sln
*.vcxproj*
# Custom SCons configuration override
/custom.py
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
x64/
build/
bld/
[Oo]bj/
*.debug
*.dSYM
# Visual Studio cache/options directory
.vs/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# Hints for improving IntelliSense, created together with VS project
cpp.hint
#NUNIT
*.VisualState.xml
TestResult.xml
*.o
*.a
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.bak
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
*.nib
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.opendb
*.VC.VC.opendb
enc_temp_folder/
# Visual Studio profiler
*.psess
*.vsp
*.vspx
# CodeLite project files
*.project
*.workspace
.codelite/
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding addin-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# NCrunch
*.ncrunch*
_NCrunch_*
.*crunch*.local.xml
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# NuGet Packages Directory
## TODO: If you have NuGet Package Restore enabled, uncomment the next line
#packages/*
## TODO: If the tool you use requires repositories.config, also uncomment the next line
#!packages/repositories.config
# Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets
# This line needs to be after the ignore of the build folder (and the packages folder if the line above has been uncommented)
!packages/build/
# Windows Azure Build Output
csx/
*.build.csdef
# Windows Store app package directory
AppPackages/
# Others
sql/
*.Cache
ClientBin/
[Ss]tyle[Cc]op.*
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.pfx
*.publishsettings
node_modules/
__pycache__/
# KDE
.directory
#Kdevelop project files
*.kdev4
# Xcode
xcuserdata/
*.xcscmblueprint
*.xccheckout
*.xcodeproj/*
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file to a newer
# Visual Studio version. Backup files are not needed, because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
App_Data/*.mdf
App_Data/*.ldf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/
# =========================
# Windows detritus
# =========================
# Windows image file caches
[Tt]humbs.db
[Tt]humbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Windows stackdumps
*.stackdump
# Windows shortcuts
*.lnk
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
logo.h
*.autosave
# https://github.com/github/gitignore/blob/master/Global/Tags.gitignore
# Ignore tags created by etags, ctags, gtags (GNU global) and cscope
TAGS
!TAGS/
tags
*.tags
!tags/
gtags.files
GTAGS
GRTAGS
GPATH
cscope.files
cscope.out
cscope.in.out
cscope.po.out
godot.creator.*
projects/
platform/windows/godot_res.res
# Visual Studio 2017 and Visual Studio Code workspace folder
/.vs
*.vscode
# Visual Studio Code workspace file
*.code-workspace
# Scons construction environment dump
.scons_env.json
# Scons cache
.cache
# Scons progress indicator
.scons_node_count
# ccls cache (https://github.com/MaskRay/ccls)
.ccls-cache/
# compile commands (https://clang.llvm.org/docs/JSONCompilationDatabase.html)
compile_commands.json
# Cppcheck
*.cppcheck

View File

@@ -0,0 +1,21 @@
# MIT License
Copyright (c) 2020-2025, Leroy Hopson and GodotXterm contributors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,715 @@
THIRD-PARTY SOFTWARE NOTICES AND INFORMATION
The GodotXterm project incorporates components from the projects listed below.
The original copyright notices and the licenses under which GodotXterm received such components are set forth below.
1. godot-cpp
2. htable
3. libtsm
4. wcwidth
%% godot-cpp NOTICES AND INFORMATION BEGIN HERE
=========================================
# MIT License
Copyright (c) 2017-present Godot Engine contributors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
=========================================
END OF godot-cpp NOTICES AND INFORMATION
%% htable NOTICES AND INFORMATION BEGIN HERE
=========================================
GNU LESSER GENERAL PUBLIC LICENSE
Version 2.1, February 1999
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
[This is the first released version of the Lesser GPL. It also counts
as the successor of the GNU Library Public License, version 2, hence
the version number 2.1.]
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
Licenses are intended to guarantee your freedom to share and change
free software--to make sure the software is free for all its users.
This license, the Lesser General Public License, applies to some
specially designated software packages--typically libraries--of the
Free Software Foundation and other authors who decide to use it. You
can use it too, but we suggest you first think carefully about whether
this license or the ordinary General Public License is the better
strategy to use in any particular case, based on the explanations
below.
When we speak of free software, we are referring to freedom of use,
not price. Our General Public Licenses are designed to make sure that
you have the freedom to distribute copies of free software (and charge
for this service if you wish); that you receive source code or can get
it if you want it; that you can change the software and use pieces of
it in new free programs; and that you are informed that you can do
these things.
To protect your rights, we need to make restrictions that forbid
distributors to deny you these rights or to ask you to surrender these
rights. These restrictions translate to certain responsibilities for
you if you distribute copies of the library or if you modify it.
For example, if you distribute copies of the library, whether gratis
or for a fee, you must give the recipients all the rights that we gave
you. You must make sure that they, too, receive or can get the source
code. If you link other code with the library, you must provide
complete object files to the recipients, so that they can relink them
with the library after making changes to the library and recompiling
it. And you must show them these terms so they know their rights.
We protect your rights with a two-step method: (1) we copyright the
library, and (2) we offer you this license, which gives you legal
permission to copy, distribute and/or modify the library.
To protect each distributor, we want to make it very clear that
there is no warranty for the free library. Also, if the library is
modified by someone else and passed on, the recipients should know
that what they have is not the original version, so that the original
author's reputation will not be affected by problems that might be
introduced by others.
Finally, software patents pose a constant threat to the existence of
any free program. We wish to make sure that a company cannot
effectively restrict the users of a free program by obtaining a
restrictive license from a patent holder. Therefore, we insist that
any patent license obtained for a version of the library must be
consistent with the full freedom of use specified in this license.
Most GNU software, including some libraries, is covered by the
ordinary GNU General Public License. This license, the GNU Lesser
General Public License, applies to certain designated libraries, and
is quite different from the ordinary General Public License. We use
this license for certain libraries in order to permit linking those
libraries into non-free programs.
When a program is linked with a library, whether statically or using
a shared library, the combination of the two is legally speaking a
combined work, a derivative of the original library. The ordinary
General Public License therefore permits such linking only if the
entire combination fits its criteria of freedom. The Lesser General
Public License permits more lax criteria for linking other code with
the library.
We call this license the "Lesser" General Public License because it
does Less to protect the user's freedom than the ordinary General
Public License. It also provides other free software developers Less
of an advantage over competing non-free programs. These disadvantages
are the reason we use the ordinary General Public License for many
libraries. However, the Lesser license provides advantages in certain
special circumstances.
For example, on rare occasions, there may be a special need to
encourage the widest possible use of a certain library, so that it
becomes a de-facto standard. To achieve this, non-free programs must
be allowed to use the library. A more frequent case is that a free
library does the same job as widely used non-free libraries. In this
case, there is little to gain by limiting the free library to free
software only, so we use the Lesser General Public License.
In other cases, permission to use a particular library in non-free
programs enables a greater number of people to use a large body of
free software. For example, permission to use the GNU C Library in
non-free programs enables many more people to use the whole GNU
operating system, as well as its variant, the GNU/Linux operating
system.
Although the Lesser General Public License is Less protective of the
users' freedom, it does ensure that the user of a program that is
linked with the Library has the freedom and the wherewithal to run
that program using a modified version of the Library.
The precise terms and conditions for copying, distribution and
modification follow. Pay close attention to the difference between a
"work based on the library" and a "work that uses the library". The
former contains code derived from the library, whereas the latter must
be combined with the library in order to run.
GNU LESSER GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License Agreement applies to any software library or other
program which contains a notice placed by the copyright holder or
other authorized party saying it may be distributed under the terms of
this Lesser General Public License (also called "this License").
Each licensee is addressed as "you".
A "library" means a collection of software functions and/or data
prepared so as to be conveniently linked with application programs
(which use some of those functions and data) to form executables.
The "Library", below, refers to any such software library or work
which has been distributed under these terms. A "work based on the
Library" means either the Library or any derivative work under
copyright law: that is to say, a work containing the Library or a
portion of it, either verbatim or with modifications and/or translated
straightforwardly into another language. (Hereinafter, translation is
included without limitation in the term "modification".)
"Source code" for a work means the preferred form of the work for
making modifications to it. For a library, complete source code means
all the source code for all modules it contains, plus any associated
interface definition files, plus the scripts used to control
compilation and installation of the library.
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running a program using the Library is not restricted, and output from
such a program is covered only if its contents constitute a work based
on the Library (independent of the use of the Library in a tool for
writing it). Whether that is true depends on what the Library does
and what the program that uses the Library does.
1. You may copy and distribute verbatim copies of the Library's
complete source code as you receive it, in any medium, provided that
you conspicuously and appropriately publish on each copy an
appropriate copyright notice and disclaimer of warranty; keep intact
all the notices that refer to this License and to the absence of any
warranty; and distribute a copy of this License along with the
Library.
You may charge a fee for the physical act of transferring a copy,
and you may at your option offer warranty protection in exchange for a
fee.
2. You may modify your copy or copies of the Library or any portion
of it, thus forming a work based on the Library, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) The modified work must itself be a software library.
b) You must cause the files modified to carry prominent notices
stating that you changed the files and the date of any change.
c) You must cause the whole of the work to be licensed at no
charge to all third parties under the terms of this License.
d) If a facility in the modified Library refers to a function or a
table of data to be supplied by an application program that uses
the facility, other than as an argument passed when the facility
is invoked, then you must make a good faith effort to ensure that,
in the event an application does not supply such function or
table, the facility still operates, and performs whatever part of
its purpose remains meaningful.
(For example, a function in a library to compute square roots has
a purpose that is entirely well-defined independent of the
application. Therefore, Subsection 2d requires that any
application-supplied function or table used by this function must
be optional: if the application does not supply it, the square
root function must still compute square roots.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Library,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Library, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote
it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Library.
In addition, mere aggregation of another work not based on the Library
with the Library (or with a work based on the Library) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may opt to apply the terms of the ordinary GNU General Public
License instead of this License to a given copy of the Library. To do
this, you must alter all the notices that refer to this License, so
that they refer to the ordinary GNU General Public License, version 2,
instead of to this License. (If a newer version than version 2 of the
ordinary GNU General Public License has appeared, then you can specify
that version instead if you wish.) Do not make any other change in
these notices.
Once this change is made in a given copy, it is irreversible for
that copy, so the ordinary GNU General Public License applies to all
subsequent copies and derivative works made from that copy.
This option is useful when you wish to copy part of the code of
the Library into a program that is not a library.
4. You may copy and distribute the Library (or a portion or
derivative of it, under Section 2) in object code or executable form
under the terms of Sections 1 and 2 above provided that you accompany
it with the complete corresponding machine-readable source code, which
must be distributed under the terms of Sections 1 and 2 above on a
medium customarily used for software interchange.
If distribution of object code is made by offering access to copy
from a designated place, then offering equivalent access to copy the
source code from the same place satisfies the requirement to
distribute the source code, even though third parties are not
compelled to copy the source along with the object code.
5. A program that contains no derivative of any portion of the
Library, but is designed to work with the Library by being compiled or
linked with it, is called a "work that uses the Library". Such a
work, in isolation, is not a derivative work of the Library, and
therefore falls outside the scope of this License.
However, linking a "work that uses the Library" with the Library
creates an executable that is a derivative of the Library (because it
contains portions of the Library), rather than a "work that uses the
library". The executable is therefore covered by this License.
Section 6 states terms for distribution of such executables.
When a "work that uses the Library" uses material from a header file
that is part of the Library, the object code for the work may be a
derivative work of the Library even though the source code is not.
Whether this is true is especially significant if the work can be
linked without the Library, or if the work is itself a library. The
threshold for this to be true is not precisely defined by law.
If such an object file uses only numerical parameters, data
structure layouts and accessors, and small macros and small inline
functions (ten lines or less in length), then the use of the object
file is unrestricted, regardless of whether it is legally a derivative
work. (Executables containing this object code plus portions of the
Library will still fall under Section 6.)
Otherwise, if the work is a derivative of the Library, you may
distribute the object code for the work under the terms of Section 6.
Any executables containing that work also fall under Section 6,
whether or not they are linked directly with the Library itself.
6. As an exception to the Sections above, you may also combine or
link a "work that uses the Library" with the Library to produce a
work containing portions of the Library, and distribute that work
under terms of your choice, provided that the terms permit
modification of the work for the customer's own use and reverse
engineering for debugging such modifications.
You must give prominent notice with each copy of the work that the
Library is used in it and that the Library and its use are covered by
this License. You must supply a copy of this License. If the work
during execution displays copyright notices, you must include the
copyright notice for the Library among them, as well as a reference
directing the user to the copy of this License. Also, you must do one
of these things:
a) Accompany the work with the complete corresponding
machine-readable source code for the Library including whatever
changes were used in the work (which must be distributed under
Sections 1 and 2 above); and, if the work is an executable linked
with the Library, with the complete machine-readable "work that
uses the Library", as object code and/or source code, so that the
user can modify the Library and then relink to produce a modified
executable containing the modified Library. (It is understood
that the user who changes the contents of definitions files in the
Library will not necessarily be able to recompile the application
to use the modified definitions.)
b) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (1) uses at run time a
copy of the library already present on the user's computer system,
rather than copying library functions into the executable, and (2)
will operate properly with a modified version of the library, if
the user installs one, as long as the modified version is
interface-compatible with the version that the work was made with.
c) Accompany the work with a written offer, valid for at least
three years, to give the same user the materials specified in
Subsection 6a, above, for a charge no more than the cost of
performing this distribution.
d) If distribution of the work is made by offering access to copy
from a designated place, offer equivalent access to copy the above
specified materials from the same place.
e) Verify that the user has already received a copy of these
materials or that you have already sent this user a copy.
For an executable, the required form of the "work that uses the
Library" must include any data and utility programs needed for
reproducing the executable from it. However, as a special exception,
the materials to be distributed need not include anything that is
normally distributed (in either source or binary form) with the major
components (compiler, kernel, and so on) of the operating system on
which the executable runs, unless that component itself accompanies
the executable.
It may happen that this requirement contradicts the license
restrictions of other proprietary libraries that do not normally
accompany the operating system. Such a contradiction means you cannot
use both them and the Library together in an executable that you
distribute.
7. You may place library facilities that are a work based on the
Library side-by-side in a single library together with other library
facilities not covered by this License, and distribute such a combined
library, provided that the separate distribution of the work based on
the Library and of the other library facilities is otherwise
permitted, and provided that you do these two things:
a) Accompany the combined library with a copy of the same work
based on the Library, uncombined with any other library
facilities. This must be distributed under the terms of the
Sections above.
b) Give prominent notice with the combined library of the fact
that part of it is a work based on the Library, and explaining
where to find the accompanying uncombined form of the same work.
8. You may not copy, modify, sublicense, link with, or distribute
the Library except as expressly provided under this License. Any
attempt otherwise to copy, modify, sublicense, link with, or
distribute the Library is void, and will automatically terminate your
rights under this License. However, parties who have received copies,
or rights, from you under this License will not have their licenses
terminated so long as such parties remain in full compliance.
9. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Library or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Library (or any work based on the
Library), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Library or works based on it.
10. Each time you redistribute the Library (or any work based on the
Library), the recipient automatically receives a license from the
original licensor to copy, distribute, link with or modify the Library
subject to these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties with
this License.
11. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Library at all. For example, if a patent
license would not permit royalty-free redistribution of the Library by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Library.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply, and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
12. If the distribution and/or use of the Library is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Library under this License
may add an explicit geographical distribution limitation excluding those
countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
13. The Free Software Foundation may publish revised and/or new
versions of the Lesser General Public License from time to time.
Such new versions will be similar in spirit to the present version,
but may differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Library
specifies a version number of this License which applies to it and
"any later version", you have the option of following the terms and
conditions either of that version or of any later version published by
the Free Software Foundation. If the Library does not specify a
license version number, you may choose any version ever published by
the Free Software Foundation.
14. If you wish to incorporate parts of the Library into other free
programs whose distribution conditions are incompatible with these,
write to the author to ask for permission. For software which is
copyrighted by the Free Software Foundation, write to the Free
Software Foundation; we sometimes make exceptions for this. Our
decision will be guided by the two goals of preserving the free status
of all derivatives of our free software and of promoting the sharing
and reuse of software generally.
NO WARRANTY
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Libraries
If you develop a new library, and you want it to be of the greatest
possible use to the public, we recommend making it free software that
everyone can redistribute and change. You can do so by permitting
redistribution under these terms (or, alternatively, under the terms
of the ordinary General Public License).
To apply these terms, attach the following notices to the library.
It is safest to attach them to the start of each source file to most
effectively convey the exclusion of warranty; and each file should
have at least the "copyright" line and a pointer to where the full
notice is found.
<one line to give the library's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Also add information on how to contact you by electronic and paper mail.
You should also get your employer (if you work as a programmer) or
your school, if any, to sign a "copyright disclaimer" for the library,
if necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the
library `Frob' (a library for tweaking knobs) written by James
Random Hacker.
<signature of Ty Coon>, 1 April 1990
Ty Coon, President of Vice
That's all there is to it!
=========================================
END OF htable NOTICES AND INFORMATION
%% libtsm NOTICES AND INFORMATION BEGIN HERE
=========================================
= Authors =
This software was written by:
Aetf <aetf@unlimitedcodeworks.xyz>
David Herrmann <dh.herrmann@gmail.com>
Fredrik Wikstrom <fredrik@a500.org>
Ran Benita <ran234@gmail.com>
= Copyright Notice =
This software is licensed under the terms of the MIT license. Please see each
source file for the related copyright notice and license.
If a file does not contain a copright notice, the following license shall
apply:
Copyright (c) 2019-2020 Fredrik Wikstrom <fredrik@a500.org>
Copyright (c) 2017-2018 Aetf <aetf@unlimitedcodeworks.xyz>
Copyright (c) 2011-2013 David Herrmann <dh.herrmann@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files
(the "Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
== Third-Party Source ==
The hash-table implementation in src/shared/shl-htable.* uses internally the
htable from CCAN, see LICENSE_htable.
The wcwidth() implementation in ./external/wcwidth is from
Copyright (C) Fredrik Fornwall 2016.
Distributed under the MIT License.
Implementation of wcwidth(3) as a C port of:
https://github.com/jquast/wcwidth
Report issues at:
https://github.com/termux/wcwidth
UCS-4 to UTF-8 encoding is copied from "terminology":
Copyright (C) 2012-2012 Carsten Haitzler and various contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
The "solarized" color palettes in tsm_vte.c are from:
Copyright (c) 2011 Ethan Schoonover
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
=========================================
END OF libtsm NOTICES AND INFORMATION
%% wcwidth NOTICES AND INFORMATION BEGIN HERE
=========================================
This project is licensed for use as follows:
"""
The MIT License (MIT)
Copyright (c) 2016 Fredrik Fornwall <fredrik@fornwall.net>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
This license applies to parts originating from the
https://github.com/jquast/wcwidth repository:
"""
The MIT License (MIT)
Copyright (c) 2014 Jeff Quast <contact@jeffquast.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
=========================================
END OF wcwidth NOTICES AND INFORMATION

View File

@@ -0,0 +1,116 @@
@tool
extends Terminal
signal exited(exit_code, signum)
var editor_settings: EditorSettings
@onready var pty = $PTY
# Sets terminal colors according to a dictionary that maps terminal color names
# to TextEditor theme color names.
func _set_terminal_colors(color_map: Dictionary) -> void:
for key in color_map.keys():
var val: String = color_map[key]
var editor_color = editor_settings.get_setting("text_editor/theme/highlighting/%s" % val)
var color: Color = editor_color if editor_color else Color.BLACK
add_theme_color_override(key, color)
func _apply_font_size(font_size: int) -> void:
add_theme_font_size_override("normal_font_size", font_size)
add_theme_font_size_override("bold_font_size", font_size)
add_theme_font_size_override("italics_font_size", font_size)
add_theme_font_size_override("bold_italics_font_size", font_size)
func _set_terminal_font() -> void:
# Try to get editor's code font and font size first.
var editor_font = null
if editor_settings.has_setting("interface/editor/code_font"):
editor_font = editor_settings.get_setting("interface/editor/code_font")
if editor_settings.has_setting("interface/editor/code_font_size"):
var editor_font_size = editor_settings.get_setting("interface/editor/code_font_size")
if editor_font_size is int:
_apply_font_size(editor_font_size)
# If we have an editor font, use it.
if editor_font and editor_font is Font:
add_theme_font_override("normal_font", editor_font)
add_theme_font_override("bold_font", editor_font)
add_theme_font_override("italics_font", editor_font)
add_theme_font_override("bold_italics_font", editor_font)
return
# Fallback to bundled monospace font.
var font_path = "res://addons/godot_xterm/themes/fonts/regular.tres"
if ResourceLoader.exists(font_path):
var default_font = load(font_path)
if default_font and default_font is Font:
add_theme_font_override("normal_font", default_font)
add_theme_font_override("bold_font", default_font)
add_theme_font_override("italics_font", default_font)
add_theme_font_override("bold_italics_font", default_font)
func _ready():
if not editor_settings:
return
# Ensure monospace font is loaded for editor terminal.
_set_terminal_font()
# Get colors from TextEdit theme. Created using the default (Adaptive) theme
# for reference, but will probably cause strange results if using another theme
# better to use a dedicated terminal theme, rather than relying on this.
_set_terminal_colors(
{
"background_color": "background_color",
"foreground_color": "text_color",
"ansi_0_color": "caret_background_color",
"ansi_1_color": "brace_mismatch_color",
"ansi_2_color": "gdscript/node_reference_color",
"ansi_3_color": "executing_line_color",
"ansi_4_color": "bookmark_color",
"ansi_5_color": "control_flow_keyword_color",
"ansi_6_color": "engine_type_color",
"ansi_7_color": "comment_color",
"ansi_8_color": "completion_background_color",
"ansi_9_color": "keyword_color",
"ansi_10_color": "base_type_color",
"ansi_11_color": "string_color",
"ansi_12_color": "function_color",
"ansi_13_color": "gdscript/global_function_color",
"ansi_14_color": "gdscript/function_definition_color",
"ansi_15_color": "caret_color",
}
)
func _input(event):
if has_focus() and event is InputEventKey and event.is_pressed():
if event.ctrl_pressed and event.keycode in [KEY_PAGEUP, KEY_PAGEDOWN]:
# Handled by switch tabs shortcut.
return
if event.ctrl_pressed and event.shift_pressed:
# Not handled by terminal.
return
# Handle all other InputEventKey events to prevent triggering of editor
# shortcuts when using the terminal.
#
# Currently the only way to get shortcuts is by calling editor_settings.get_setting("shortcuts")
# and it returns an array that *only* contains shortcuts that have been modified from the original.
# Once https://github.com/godotengine/godot-proposals/issues/4112 is resolved it should be possible
# to get all shortcuts by their editor setting string as documented here:
# https://docs.godotengine.org/en/stable/tutorials/editor/default_key_mapping.html.
# In this case we could simply add a setting called something like "allowed shortcuts" or
# "propagated shortcuts" consisting of an array of shortcut editor setting strings.
# For example "editor/save_scene" which saves the scene and by default maps to 'Ctrl + S'.
# Then any shortcut events listed here can be handled by the terminal *and* the editor.
func _on_PTY_exited(exit_code: int, signum: int):
emit_signal("exited", exit_code, signum)

View File

@@ -0,0 +1 @@
uid://cgjg4p52appdp

View File

@@ -0,0 +1,26 @@
[gd_scene load_steps=2 format=3 uid="uid://bkcyv0w3setep"]
[ext_resource type="Script" uid="uid://cgjg4p52appdp" path="res://addons/godot_xterm/editor_plugins/terminal/editor_terminal.gd" id="1"]
[node name="Terminal" type="Terminal"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
size_flags_horizontal = 4
size_flags_vertical = 4
focus_mode = 1
script = ExtResource("1")
[node name="PTY" type="PTY" parent="."]
cols = 114
rows = 29
terminal_path = NodePath("..")
[node name="Bell" type="AudioStreamPlayer" parent="."]
[connection signal="data_sent" from="." to="PTY" method="write"]
[connection signal="size_changed" from="." to="PTY" method="resizev"]
[connection signal="data_received" from="PTY" to="." method="write"]
[connection signal="exited" from="PTY" to="." method="_on_PTY_exited"]

View File

@@ -0,0 +1,9 @@
[gd_resource type="Shortcut" load_steps=2 format=3 uid="uid://cdr3iu6ttahlt"]
[sub_resource type="InputEventKey" id="InputEventKey_emaic"]
shift_pressed = true
ctrl_pressed = true
keycode = 67
[resource]
events = [SubResource("InputEventKey_emaic")]

View File

@@ -0,0 +1,9 @@
[gd_resource type="Shortcut" load_steps=2 format=3 uid="uid://b5vans7f0vp2y"]
[sub_resource type="InputEventKey" id="InputEventKey_7k47m"]
shift_pressed = true
ctrl_pressed = true
keycode = 88
[resource]
events = [SubResource("InputEventKey_7k47m")]

View File

@@ -0,0 +1,9 @@
[gd_resource type="Shortcut" load_steps=2 format=3 uid="uid://d3iu5cplfailp"]
[sub_resource type="InputEventKey" id="InputEventKey_0hq5g"]
shift_pressed = true
ctrl_pressed = true
keycode = 84
[resource]
events = [SubResource("InputEventKey_0hq5g")]

View File

@@ -0,0 +1,9 @@
[gd_resource type="Shortcut" load_steps=2 format=3 uid="uid://cn2b3aeang6b"]
[sub_resource type="InputEventKey" id="InputEventKey_sihkr"]
shift_pressed = true
ctrl_pressed = true
keycode = 86
[resource]
events = [SubResource("InputEventKey_sihkr")]

View File

@@ -0,0 +1,8 @@
[gd_resource type="Shortcut" load_steps=2 format=3 uid="uid://dpb1qwlv6mkfs"]
[sub_resource type="InputEventKey" id="InputEventKey_v5b4d"]
ctrl_pressed = true
keycode = 4194324
[resource]
events = [SubResource("InputEventKey_v5b4d")]

View File

@@ -0,0 +1,8 @@
[gd_resource type="Shortcut" load_steps=2 format=3 uid="uid://djs5hhu8vbmqf"]
[sub_resource type="InputEventKey" id="InputEventKey_svjos"]
ctrl_pressed = true
keycode = 4194323
[resource]
events = [SubResource("InputEventKey_svjos")]

View File

@@ -0,0 +1,63 @@
extends Resource
enum FileType {
USE_SHELL_ENV,
CUSTOM_FILE,
}
enum CWDType {
USE_PROJECT_DIRECTORY,
CUSTOM_CWD,
}
### Shortcuts ###
@export var new_terminal_shortcut: Shortcut = preload("./default_new_terminal_shortcut.tres")
@export var kill_terminal_shortcut: Shortcut = preload("./default_kill_terminal_shortcut.tres")
@export var copy_shortcut: Shortcut = preload("./default_copy_shortcut.tres")
@export var paste_shortcut: Shortcut = preload("./default_paste_shortcut.tres")
@export var next_tab_shortcut: Shortcut = preload("./default_tab_right_shortcut.tres")
@export var previous_tab_shortcut: Shortcut = preload("./default_tab_left_shortcut.tres")
### Scroll settings ###
# The maximum amount of lines the terminal keeps in its buffer.
@export var scrollback_buffer_lines := 1000
# If true, mouse wheel up and down can be used to scroll the terminal.
@export var mouse_wheel_scroll := true
# Whether or not to display scroll bar.
@export var show_scroll_bar := true
# Copy/paste settings.
@export var copy_on_selection := false
# Font settings.
@export var font_size: int = 14
@export var letter_spacing: int = 0
@export var line_height: float = 1.2
@export var ctrl_scroll_to_resize_font := true
# Bell settings.
@export var visual_bell := true
@export var audio_bell := true
@export var bell_sound: AudioStream
# Exec args.
@export var file_type := FileType.USE_SHELL_ENV
@export var custom_file := "/bin/sh"
@export var cwd_type := CWDType.USE_PROJECT_DIRECTORY
@export var custom_cwd := ""
@export var args := PackedStringArray()
@export var use_os_env := true
@export var extra_env := {
TERM = "xterm-256color",
COLORTERM = "truecolor",
}
func _init(p_copy_on_selection := false):
copy_on_selection = p_copy_on_selection

View File

@@ -0,0 +1,290 @@
# Copyright (c) 2021, Leroy Hopson (MIT License).
#
# This file contains snippets of code derived from Godot's editor_node.cpp file.
# These snippets are copyright of their authors and released under the MIT license:
# - Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur (MIT License).
# - Copyright (c) 2014-2021 Godot Engine contributors (MIT License).
@tool
extends Control
const EditorTerminal := preload("./editor_terminal.tscn")
const TerminalSettings := preload("./settings/terminal_settings.gd")
const SETTINGS_FILE_PATH := "res://.gdxterm/settings.tres"
enum TerminalPopupMenuOptions {
NEW_TERMINAL = 0,
COPY = 2,
PASTE = 3,
SELECT_ALL = 4,
CLEAR = 6,
KILL_TERMINAL = 7,
}
# Has access to the EditorSettings singleton so it can dynamically generate the
# terminal color scheme based on editor theme settings.
var editor_plugin: EditorPlugin
var editor_interface: EditorInterface
var editor_settings: EditorSettings
@onready var tabs: TabBar = $VBoxContainer/TabbarContainer/Tabs
@onready var tabbar_container: HBoxContainer = $VBoxContainer/TabbarContainer
@onready var add_button: Button = $VBoxContainer/TabbarContainer/AddButton
@onready var tab_container: TabContainer = $VBoxContainer/TabContainer
@onready var terminal_popup_menu: PopupMenu = $VBoxContainer/TerminalPopupMenu
# Size label.
# Used to show the size of the terminal (rows/cols) and panel (pixels) when resized.
@onready var size_label: Label = $SizeLabel
@onready var size_label_timer: Timer = $SizeLabel/SizeLabelTimer
@onready var is_ready := true
var _theme := Theme.new()
var _settings: TerminalSettings
var _tab_container_min_size
func _ready():
if editor_interface:
add_button.set("icon", get_theme_icon("Add", "EditorIcons"))
editor_settings = editor_interface.get_editor_settings()
_update_settings()
func _load_or_create_settings() -> void:
# Use only default settings for now, until settings are properly defined
# and documented.
_settings = TerminalSettings.new()
func _update_settings() -> void:
_load_or_create_settings()
var editor_scale: float = 1.0
if editor_interface and editor_interface.has_method("get_editor_scale"):
editor_scale = editor_interface.get_editor_scale()
custom_minimum_size = Vector2(0, tabbar_container.size.y + 182) * editor_scale
call_deferred("set_size", Vector2(size.x, 415))
tabs.tab_close_display_policy = TabBar.CLOSE_BUTTON_SHOW_ALWAYS
# Update shortcuts.
if _settings.new_terminal_shortcut:
terminal_popup_menu.set_item_shortcut(
TerminalPopupMenuOptions.NEW_TERMINAL, _settings.new_terminal_shortcut, true
)
if _settings.kill_terminal_shortcut:
terminal_popup_menu.set_item_shortcut(
TerminalPopupMenuOptions.KILL_TERMINAL, _settings.kill_terminal_shortcut, false
)
if _settings.copy_shortcut:
terminal_popup_menu.set_item_shortcut(
TerminalPopupMenuOptions.COPY, _settings.copy_shortcut, false
)
if _settings.paste_shortcut:
terminal_popup_menu.set_item_shortcut(
TerminalPopupMenuOptions.PASTE, _settings.paste_shortcut, false
)
_update_terminal_tabs()
func _update_terminal_tabs():
# Wait a couple of frames to allow everything to resize before updating.
var tree = get_tree()
if tree:
await tree.process_frame
await tree.process_frame
if tabs.get_offset_buttons_visible():
# Move add button to fixed position at the right of the tabbar container.
if add_button.get_parent() == tabs:
tabs.remove_child(add_button)
tabbar_container.add_child(add_button)
# Keep it at the end (right side) of the container.
else:
# Move add button after last tab.
if tabs.tab_count > 0 and add_button.get_parent() == tabbar_container:
tabbar_container.remove_child(add_button)
tabs.add_child(add_button)
var last_tab := Rect2()
if tabs.get_tab_count() > 0:
last_tab = tabs.get_tab_rect(tabs.get_tab_count() - 1)
add_button.position = Vector2(
last_tab.position.x + last_tab.size.x + 3, last_tab.position.y
)
if tabs.tab_count == 0 and add_button.get_parent() == tabs:
tabs.remove_child(add_button)
tabbar_container.add_child(add_button)
tabbar_container.move_child(add_button, 0) # Move to start (left side)
add_button.position = Vector2.ZERO
# Make sure we still own the button, so it gets saved with our scene.
add_button.owner = self
func _on_AddButton_pressed():
var shell = (
OS.get_environment("SHELL")
if OS.has_environment("SHELL")
else ("powershell" if OS.get_name() == "Windows" else "sh")
)
var terminal := EditorTerminal.instantiate()
tabs.add_tab(shell.get_file())
terminal.editor_settings = editor_settings
terminal.set_anchors_preset(PRESET_BOTTOM_WIDE)
terminal.connect("gui_input", Callable(self, "_on_TabContainer_gui_input"))
terminal.connect("exited", Callable(self, "_on_Terminal_exited").bind(terminal))
tab_container.add_child(terminal)
terminal.pty.fork(shell)
terminal.grab_focus()
tabs.current_tab = tabs.get_tab_count() - 1
tab_container.current_tab = tabs.current_tab
_update_terminal_tabs()
func _on_Tabs_tab_changed(tab_index):
# Simply sync the TabContainer - focus handling happens in TabContainer signal
# Only sync if TabContainer has enough tabs
if tab_index < tab_container.get_tab_count():
tab_container.current_tab = tab_index
func _on_TabContainer_tab_changed(tab_index):
# TabContainer has already changed, so we can safely focus immediately
var current_tab_control = tab_container.get_current_tab_control()
if current_tab_control:
current_tab_control.grab_focus()
func _on_Tabs_tab_close(tab_index):
tabs.remove_tab(tab_index)
tab_container.get_child(tab_index).queue_free()
# Sync TabContainer to the current TabBar selection
# Focus will be handled automatically by the TabContainer signal
if tabs.get_tab_count() > 0:
tab_container.current_tab = tabs.current_tab
_update_terminal_tabs()
func _notification(what):
if not is_ready:
return
match what:
EditorSettings.NOTIFICATION_EDITOR_SETTINGS_CHANGED:
_update_settings()
_update_terminal_tabs()
NOTIFICATION_RESIZED:
_update_terminal_tabs()
NOTIFICATION_APPLICATION_FOCUS_IN:
_update_terminal_tabs()
func _input(event: InputEvent) -> void:
if not _settings or not event.is_pressed():
return
# Global shortcut to open new terminal and make terminal panel visible.
if _settings.new_terminal_shortcut and _settings.new_terminal_shortcut.matches_event(event):
get_viewport().set_input_as_handled()
editor_plugin.make_bottom_panel_item_visible(self)
_on_AddButton_pressed()
# Non-global shortcuts, only applied if terminal is active and focused.
if (
tabs.get_tab_count() > 0 and tab_container.get_child(tabs.current_tab).has_focus()
or terminal_popup_menu.has_focus()
):
# Kill terminal.
if (
_settings.kill_terminal_shortcut
and _settings.kill_terminal_shortcut.matches_event(event)
):
get_viewport().set_input_as_handled()
_on_TerminalPopupMenu_id_pressed(TerminalPopupMenuOptions.KILL_TERMINAL)
# Copy.
if _settings.copy_shortcut and _settings.copy_shortcut.matches_event(event):
get_viewport().set_input_as_handled()
_on_TerminalPopupMenu_id_pressed(TerminalPopupMenuOptions.COPY)
# Paste.
if _settings.paste_shortcut and _settings.paste_shortcut.matches_event(event):
get_viewport().set_input_as_handled()
_on_TerminalPopupMenu_id_pressed(TerminalPopupMenuOptions.PASTE)
# Next tab.
if _settings.next_tab_shortcut and _settings.next_tab_shortcut.matches_event(event):
get_viewport().set_input_as_handled()
tabs.current_tab = min(tabs.current_tab + 1, tabs.get_tab_count() - 1)
# Previous tab.
if _settings.previous_tab_shortcut and _settings.previous_tab_shortcut.matches_event(event):
get_viewport().set_input_as_handled()
tabs.current_tab = max(tabs.current_tab - 1, 0)
func _on_TabContainer_gui_input(event):
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_RIGHT:
terminal_popup_menu.position = event.global_position
terminal_popup_menu.popup()
func _on_TerminalPopupMenu_id_pressed(id):
match id:
TerminalPopupMenuOptions.NEW_TERMINAL:
_on_AddButton_pressed()
if tabs.get_tab_count() > 0:
var terminal = tab_container.get_child(tab_container.current_tab)
match id:
TerminalPopupMenuOptions.COPY:
DisplayServer.clipboard_set(terminal.copy_selection())
TerminalPopupMenuOptions.PASTE:
terminal.write(DisplayServer.clipboard_get())
TerminalPopupMenuOptions.SELECT_ALL:
terminal.select(0, 0, terminal.get_rows(), terminal.get_cols())
TerminalPopupMenuOptions.CLEAR:
terminal.clear()
TerminalPopupMenuOptions.KILL_TERMINAL:
_on_Tabs_tab_close(tabs.current_tab)
func _on_Tabs_reposition_active_tab_request(idx_to):
var active = tab_container.get_child(tab_container.current_tab)
tab_container.move_child(active, idx_to)
func _on_Panel_resized():
if not size_label:
return
var size = tab_container.size
if tabs.get_tab_count() > 0:
var terminal = tab_container.get_child(tabs.current_tab)
var cols = terminal.get_cols()
var rows = terminal.get_rows()
size_label.text = "Size: %d cols; %d rows\n(%d x %d px)" % [cols, rows, size.x, size.y]
else:
size_label.text = "Size:\n(%d x %d px)" % [size.x, size.y]
size_label.visible = true
size_label_timer.wait_time = 1
size_label_timer.start()
func _on_SizeLabelTimer_timeout():
if size_label:
size_label.visible = false
func _on_Terminal_exited(exit_code, signum, terminal):
# Leave non-zero exit code terminals open in case they have some important
# error information.
if exit_code == 0:
_on_Tabs_tab_close(terminal.get_index())

View File

@@ -0,0 +1 @@
uid://cufcnr6pxqofk

View File

@@ -0,0 +1,135 @@
[gd_scene load_steps=5 format=3 uid="uid://cbxovnvw5o4mo"]
[ext_resource type="Script" uid="uid://cufcnr6pxqofk" path="res://addons/godot_xterm/editor_plugins/terminal/terminal_panel.gd" id="1"]
[sub_resource type="Image" id="Image_stuna"]
data = {
"data": PackedByteArray(255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 92, 92, 0, 255, 92, 92, 0, 255, 92, 92, 0, 255, 92, 92, 0, 255, 92, 92, 0, 255, 92, 92, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 0, 255, 92, 92, 127, 255, 92, 92, 0, 255, 92, 92, 0, 255, 92, 92, 0, 255, 92, 92, 0, 255, 92, 92, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 0, 255, 93, 93, 255, 255, 92, 92, 127, 255, 92, 92, 0, 255, 92, 92, 0, 255, 92, 92, 0, 255, 92, 92, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 92, 92, 127, 255, 92, 92, 0, 255, 92, 92, 0, 255, 92, 92, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 92, 92, 127, 255, 92, 92, 0, 255, 92, 92, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 92, 92, 0, 255, 92, 92, 0, 255, 92, 92, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 231, 255, 90, 90, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 90, 90, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 0, 255, 93, 93, 0, 255, 91, 91, 0, 255, 91, 91, 0, 255, 91, 91, 42, 255, 90, 90, 0, 255, 94, 94, 0, 255, 91, 91, 42, 255, 93, 93, 233, 255, 92, 92, 232, 255, 93, 93, 41, 255, 90, 90, 0, 255, 94, 94, 0, 255, 91, 91, 42, 255, 93, 93, 233, 255, 92, 92, 232, 255, 92, 92, 0, 255, 92, 92, 0, 255, 91, 91, 0, 255, 91, 91, 0, 255, 91, 91, 0, 255, 91, 91, 45, 255, 93, 93, 44, 255, 91, 91, 0, 255, 91, 91, 42, 255, 91, 91, 42, 255, 93, 93, 0, 255, 91, 91, 45, 255, 93, 93, 44, 255, 91, 91, 0, 255, 91, 91, 42, 255, 91, 91, 42, 255, 91, 91, 0, 255, 91, 91, 0, 255, 91, 91, 0, 255, 91, 91, 0, 255, 91, 91, 45, 255, 92, 92, 235, 255, 92, 92, 234, 255, 89, 89, 43, 255, 91, 91, 0, 255, 91, 91, 0, 255, 91, 91, 45, 255, 92, 92, 235, 255, 92, 92, 234, 255, 89, 89, 43, 255, 91, 91, 0, 255, 91, 91, 0, 255, 91, 91, 0, 255, 91, 91, 0, 255, 92, 92, 0, 255, 92, 92, 0, 255, 92, 92, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 91, 91, 59, 255, 92, 92, 61, 255, 92, 92, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 91, 91, 59, 255, 92, 92, 61, 255, 92, 92, 0, 255, 92, 92, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0),
"format": "RGBA8",
"height": 16,
"mipmaps": false,
"width": 16
}
[sub_resource type="ImageTexture" id="ImageTexture_umfb5"]
image = SubResource("Image_stuna")
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_osmrc"]
bg_color = Color(0.113329, 0.129458, 0.156802, 1)
[node name="Panel" type="Panel"]
custom_minimum_size = Vector2(0, 206)
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_right = 2304.0
offset_bottom = -233.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1")
[node name="VBoxContainer" type="VBoxContainer" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/separation = 0
[node name="TabbarContainer" type="HBoxContainer" parent="VBoxContainer"]
layout_mode = 2
[node name="Tabs" type="TabBar" parent="VBoxContainer/TabbarContainer"]
layout_mode = 2
size_flags_horizontal = 3
focus_mode = 0
tab_close_display_policy = 2
drag_to_rearrange_enabled = true
[node name="AddButton" type="Button" parent="VBoxContainer/TabbarContainer"]
layout_mode = 2
tooltip_text = "New Terminal (Ctrl+Shift+T)"
theme_override_colors/icon_normal_color = Color(0.6, 0.6, 0.6, 0.8)
flat = true
[node name="PopupMenu" type="PopupMenu" parent="VBoxContainer/TabbarContainer"]
item_count = 4
item_0/text = "Kill"
item_0/id = 0
item_1/text = "Kill Others"
item_1/id = 1
item_2/text = "Kill to the Right"
item_2/id = 2
item_3/text = "Kill All"
item_3/id = 3
[node name="TabContainer" type="TabContainer" parent="VBoxContainer"]
clip_contents = true
layout_mode = 2
size_flags_vertical = 3
theme_override_styles/panel = SubResource("StyleBoxFlat_osmrc")
tabs_visible = false
deselect_enabled = true
[node name="TerminalPopupMenu" type="PopupMenu" parent="VBoxContainer"]
size = Vector2i(136, 178)
item_count = 8
item_0/text = "New Terminal"
item_0/id = 0
item_1/id = 1
item_1/disabled = true
item_1/separator = true
item_2/text = "Copy"
item_2/id = 2
item_3/text = "Paste"
item_3/id = 3
item_4/text = "Select All"
item_4/id = 4
item_5/id = 5
item_5/separator = true
item_6/text = "Clear"
item_6/id = 6
item_7/text = "Kill Terminal"
item_7/id = 7
[node name="SizeLabel" type="Label" parent="."]
visible = false
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -52.0
offset_top = -15.5
offset_right = 52.0
offset_bottom = 15.5
grow_horizontal = 2
grow_vertical = 2
text = "Size:
(3456 x 1035 px)"
[node name="Panel" type="Panel" parent="SizeLabel"]
show_behind_parent = true
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = -5.0
offset_top = -5.0
offset_right = 5.0
offset_bottom = 5.0
grow_horizontal = 2
grow_vertical = 2
[node name="SizeLabelTimer" type="Timer" parent="SizeLabel"]
[connection signal="resized" from="." to="." method="_on_Panel_resized"]
[connection signal="tab_changed" from="VBoxContainer/TabbarContainer/Tabs" to="." method="_on_Tabs_tab_changed"]
[connection signal="tab_close_pressed" from="VBoxContainer/TabbarContainer/Tabs" to="." method="_on_Tabs_tab_close"]
[connection signal="pressed" from="VBoxContainer/TabbarContainer/AddButton" to="." method="_on_AddButton_pressed"]
[connection signal="gui_input" from="VBoxContainer/TabContainer" to="." method="_on_TabContainer_gui_input"]
[connection signal="tab_changed" from="VBoxContainer/TabContainer" to="." method="_on_TabContainer_tab_changed"]
[connection signal="id_pressed" from="VBoxContainer/TerminalPopupMenu" to="." method="_on_TerminalPopupMenu_id_pressed"]
[connection signal="timeout" from="SizeLabel/SizeLabelTimer" to="." method="_on_SizeLabelTimer_timeout"]

View File

@@ -0,0 +1,24 @@
[configuration]
entry_symbol = "godot_xterm_library_init"
compatibility_minimum = "4.2.0"
[icons]
Terminal = "res://addons/godot_xterm/icons/terminal_icon.svg"
PTY = "res://addons/godot_xterm/icons/pty_icon.svg"
[libraries]
linux.debug.x86_64 = "res://addons/godot_xterm/bin/libgodot-xterm.linux.template_debug.x86_64.so"
linux.release.x86_64 = "res://addons/godot_xterm/bin/libgodot-xterm.linux.template_release.x86_64.so"
linux.debug.x86_32 = "res://addons/godot_xterm/bin/libgodot-xterm.linux.template_debug.x86_32.so"
linux.release.x86_32 = "res://addons/godot_xterm/bin/libgodot-xterm.linux.template_release.x86_32.so"
web.debug.wasm32 = "res://addons/godot_xterm/bin/libgodot-xterm.web.template_debug.wasm32.wasm"
web.release.wasm32 = "res://addons/godot_xterm/bin/libgodot-xterm.web.template_release.wasm32.wasm"
macos.debug = "res://addons/godot_xterm/bin/libgodot-xterm.macos.template_debug.framework"
macos.release = "res://addons/godot_xterm/bin/libgodot-xterm.macos.template_release.framework"
windows.debug.x86_64 = "res://addons/godot_xterm/bin/libgodot-xterm.windows.template_debug.x86_64.dll"
windows.release.x86_64 = "res://addons/godot_xterm/bin/libgodot-xterm.windows.template_release.x86_64.dll"
windows.debug.x86_32 = "res://addons/godot_xterm/bin/libgodot-xterm.windows.template_debug.x86_32.dll"
windows.release.x86_32 = "res://addons/godot_xterm/bin/libgodot-xterm.windows.template_release.x86_32.dll"

View File

@@ -0,0 +1 @@
uid://xlyw0p2epn3l

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
id="svg4716"
version="1.1"
width="16"
viewBox="0 0 16 16"
height="16">
<metadata
id="metadata4722">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs4720">
<clipPath
id="clipPath826"
clipPathUnits="userSpaceOnUse">
<use
height="100%"
width="100%"
id="use828"
xlink:href="#g822"
y="0"
x="0" />
</clipPath>
</defs>
<g
clip-path="url(#clipPath826)"
id="g824">
<g
id="g822">
<path
style="fill:#e0e0e0;fill-opacity:0.99607999"
d="M 4,1 1,5 H 3 V 8 H 5 V 5 h 2 z m 7,0 V 4 H 9 l 3,4 3,-4 H 13 V 1 Z m -2,9 v 1 h 1 v 4 h 1 v -4 h 1 v -1 z m -4,0 v 2 1 2 H 6 V 13 H 7 8 V 12 10 H 6 Z m 1,1 h 1 v 1 H 6 Z"
id="path4714" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,38 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://ixravk0w4lkt"
path="res://.godot/imported/pty_icon.svg-7c3f500292e2f95935d23c435d6de47f.ctex"
metadata={
"has_editor_variant": true,
"vram_texture": false
}
[deps]
source_file="res://addons/godot_xterm/icons/pty_icon.svg"
dest_files=["res://.godot/imported/pty_icon.svg-7c3f500292e2f95935d23c435d6de47f.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0
svg/scale=1.0
editor/scale_with_editor_scale=true
editor/convert_colors_with_editor_theme=false

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<metadata>
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
<dc:title/>
</cc:Work>
</rdf:RDF>
</metadata>
<path d="m4.5605 3.9746c-0.074558 0-0.14819 0.029049-0.20508 0.085938l-0.27539 0.27539c-0.11354 0.11358-0.11332 0.29631 0 0.41016l1.8691 1.8789-1.8691 1.8789c-0.11336 0.11385-0.11358 0.29657 0 0.41016l0.27539 0.27539c0.11377 0.11378 0.29833 0.11378 0.41211 0l2.3594-2.3594c0.11378-0.11378 0.11378-0.29834 0-0.41211l-2.3594-2.3574c-0.056882-0.056888-0.13247-0.085938-0.20703-0.085938zm3.2207 4.3984c-0.1609 0-0.29102 0.13012-0.29102 0.29102v0.38867c0 0.1609 0.13012 0.29102 0.29102 0.29102h3.6914c0.1609 0 0.29102-0.13012 0.29102-0.29102v-0.38867c0-0.1609-0.13012-0.29102-0.29102-0.29102z" fill="#a5efac" stroke-width=".012139"/>
<path d="m3 1c-1.1046 0-2 0.8954-2 2v10c0 1.1046 0.89543 2 2 2h10c1.1046 0 2-0.8954 2-2v-10c0-1.1046-0.89543-2-2-2zm0 2h10v10h-10z" fill="#a5efac"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,38 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://b1cq080dxs1ft"
path="res://.godot/imported/terminal_icon.svg-2a4b198a0e3aa43f04b43cec3e3f109d.ctex"
metadata={
"has_editor_variant": true,
"vram_texture": false
}
[deps]
source_file="res://addons/godot_xterm/icons/terminal_icon.svg"
dest_files=["res://.godot/imported/terminal_icon.svg-2a4b198a0e3aa43f04b43cec3e3f109d.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0
svg/scale=1.0
editor/scale_with_editor_scale=true
editor/convert_colors_with_editor_theme=false

View File

@@ -0,0 +1,90 @@
@tool
extends EditorImportPlugin
const Asciicast = preload("../resources/asciicast.gd")
func _get_importer_name():
return "godot_xterm"
func _get_visible_name():
return "asciicast"
func _get_recognized_extensions():
return ["cast"]
func _get_save_extension():
return "res"
func _get_resource_type():
return "Animation"
func _get_import_options(preset, _i):
return []
func _get_priority():
return 1.0
func _get_import_order():
return 0
func _get_preset_count():
return 0
func _import(source_file, save_path, options, r_platform_variant, r_gen_files):
var file = FileAccess.open(source_file, FileAccess.READ)
var err = FileAccess.get_open_error()
if err != OK:
return err
var header = file.get_line()
var asciicast = Asciicast.new()
asciicast.add_track(Animation.TYPE_METHOD, 0)
asciicast.track_set_path(0, ".")
var frame = {"time": 0.0, "data": {"method": "write", "args": [PackedByteArray()]}}
while not file.eof_reached():
var line = file.get_line()
if line == "":
continue
var test_json_conv = JSON.new()
test_json_conv.parse(line)
var p = test_json_conv.get_data()
if typeof(p) != TYPE_ARRAY:
continue
var event_type: String = p[1]
var event_data: PackedByteArray = p[2].to_utf8_buffer()
# Asciicast recordings have a resolution of 0.000001, however animation
# track keys only have a resolution of 0.01, therefore we must combine
# events that would occur in the same keyframe, otherwise only the last
# event is inserted and the previous events are overwritten.
var time = snapped(p[0], 0.01)
if event_type == "o":
if time == frame.time:
asciicast.track_remove_key_at_time(0, time)
frame.data.args[0] = frame.data.args[0] + event_data
else:
frame.time = time
frame.data.args = [event_data]
asciicast.track_insert_key(0, frame.time, frame.data)
asciicast.length = frame.time
return ResourceSaver.save(asciicast, "%s.%s" % [save_path, _get_save_extension()])

View File

@@ -0,0 +1 @@
uid://ghf570kuknhi

View File

@@ -0,0 +1,135 @@
@tool
extends EditorImportPlugin
const XrdbTheme := preload("../resources/xrdb_theme.gd")
func _get_importer_name():
return "godot_xterm_xrdb_importer"
func _get_visible_name():
return "xrdb_theme"
func _get_recognized_extensions():
return ["xrdb", "Xresources", "xresources"]
func _get_save_extension():
return "res"
func _get_resource_type():
return "Theme"
func _get_import_options(preset, _i):
return []
func _get_priority():
return 1.0
func _get_import_order():
return 0
func _get_preset_count():
return 0
func _import(source_file, save_path, options, r_platform_variant, r_gen_files):
var file = FileAccess.open(source_file, FileAccess.READ)
var err = FileAccess.get_open_error()
if err != OK:
return err
var theme: Theme = XrdbTheme.new()
theme.set_theme_item(Theme.DATA_TYPE_FONT_SIZE, "font_size", "Terminal", 14)
theme.set_theme_item(
Theme.DATA_TYPE_FONT, "normal_font", "Terminal", preload("../themes/fonts/regular.tres")
)
var word_regex = RegEx.new()
word_regex.compile("\\S+")
var color_regex = RegEx.new()
color_regex.compile(".*(?<name>cursor|foreground|background|color\\d+):")
while not file.eof_reached():
var line = file.get_line().strip_edges()
var words = word_regex.search_all(line)
if words.size() < 2:
continue
var name: String
var color: Color
if words.size() == 2:
if "cursorColor" in words[0].get_string():
name = "cursorcolor"
color = Color(words[1].get_string())
else:
var c = color_regex.search_all(words[0].get_string().to_lower())
if c.size() > 0:
name = c[0].get_string("name").to_lower()
color = Color(words[1].get_string())
if words.size() == 3 and words[0].get_string() == "#define":
name = words[1].get_string().to_lower()
color = Color(words[2].get_string())
if name == null or color == null:
continue
match name:
"color0", "ansi_0_color":
theme.set_theme_item(Theme.DATA_TYPE_COLOR, "ansi_0_color", "Terminal", color)
"color1", "ansi_1_color":
theme.set_theme_item(Theme.DATA_TYPE_COLOR, "ansi_1_color", "Terminal", color)
"color2", "ansi_2_color":
theme.set_theme_item(Theme.DATA_TYPE_COLOR, "ansi_2_color", "Terminal", color)
"color3", "ansi_3_color":
theme.set_theme_item(Theme.DATA_TYPE_COLOR, "ansi_3_color", "Terminal", color)
"color4", "ansi_4_color":
theme.set_theme_item(Theme.DATA_TYPE_COLOR, "ansi_4_color", "Terminal", color)
"color5", "ansi_5_color":
theme.set_theme_item(Theme.DATA_TYPE_COLOR, "ansi_5_color", "Terminal", color)
"color6", "ansi_6_color":
theme.set_theme_item(Theme.DATA_TYPE_COLOR, "ansi_6_color", "Terminal", color)
"color7", "ansi_7_color":
theme.set_theme_item(Theme.DATA_TYPE_COLOR, "ansi_7_color", "Terminal", color)
"color8", "ansi_8_color":
theme.set_theme_item(Theme.DATA_TYPE_COLOR, "ansi_8_color", "Terminal", color)
"color9", "ansi_9_color":
theme.set_theme_item(Theme.DATA_TYPE_COLOR, "ansi_9_color", "Terminal", color)
"color10", "ansi_10_color":
theme.set_theme_item(Theme.DATA_TYPE_COLOR, "ansi_10_color", "Terminal", color)
"color11", "ansi_11_color":
theme.set_theme_item(Theme.DATA_TYPE_COLOR, "ansi_11_color", "Terminal", color)
"color12", "ansi_12_color":
theme.set_theme_item(Theme.DATA_TYPE_COLOR, "ansi_12_color", "Terminal", color)
"color13", "ansi_13_color":
theme.set_theme_item(Theme.DATA_TYPE_COLOR, "ansi_13_color", "Terminal", color)
"color14", "ansi_14_color":
theme.set_theme_item(Theme.DATA_TYPE_COLOR, "ansi_14_color", "Terminal", color)
"color15", "ansi_15_color":
theme.set_theme_item(Theme.DATA_TYPE_COLOR, "ansi_15_color", "Terminal", color)
"foreground", "foreground_color":
theme.set_theme_item(Theme.DATA_TYPE_COLOR, "foreground_color", "Terminal", color)
"background", "background_color":
theme.set_theme_item(Theme.DATA_TYPE_COLOR, "background_color", "Terminal", color)
"selection_color":
theme.set_theme_item(Theme.DATA_TYPE_COLOR, "selection_color", "Terminal", color)
"selected_text_color":
theme.set_theme_item(
Theme.DATA_TYPE_COLOR, "selected_text_color", "Terminal", color
)
"cursorcolor", "cursor_color":
theme.set_theme_item(Theme.DATA_TYPE_COLOR, "cursor_color", "Terminal", color)
"cursor_text_color":
theme.set_theme_item(Theme.DATA_TYPE_COLOR, "cursor_text_color", "Terminal", color)
return ResourceSaver.save(theme, "%s.%s" % [save_path, _get_save_extension()])

View File

@@ -0,0 +1 @@
uid://dgssqoaaxy4xh

View File

@@ -0,0 +1,7 @@
[plugin]
name="GodotXterm"
description=""
author="Leroy Hopson"
version="4.0.0-rc.2"
script="plugin.gd"

View File

@@ -0,0 +1,40 @@
@tool
extends EditorPlugin
var pty_supported := (
OS.get_name() in ["Linux", "FreeBSD", "NetBSD", "OpenBSD", "BSD", "macOS", "Windows"]
)
var asciicast_import_plugin
var xrdb_import_plugin
var terminal_panel: Control
func _enter_tree():
asciicast_import_plugin = preload("./import_plugins/asciicast_import_plugin.gd").new()
add_import_plugin(asciicast_import_plugin)
xrdb_import_plugin = preload("./import_plugins/xrdb_import_plugin.gd").new()
add_import_plugin(xrdb_import_plugin)
var asciicast_script = preload("./resources/asciicast.gd")
add_custom_type("Asciicast", "Animation", asciicast_script, null)
if pty_supported:
terminal_panel = preload("./editor_plugins/terminal/terminal_panel.tscn").instantiate()
terminal_panel.editor_plugin = self
terminal_panel.editor_interface = get_editor_interface()
add_control_to_bottom_panel(terminal_panel, "Terminal")
func _exit_tree():
remove_import_plugin(asciicast_import_plugin)
asciicast_import_plugin = null
remove_import_plugin(xrdb_import_plugin)
xrdb_import_plugin = null
remove_custom_type("Asciicast")
if pty_supported:
remove_control_from_bottom_panel(terminal_panel)
terminal_panel.free()

View File

@@ -0,0 +1 @@
uid://ds0khigrtnemq

View File

@@ -0,0 +1,22 @@
extends Animation
signal data_written(data)
signal data_read(data)
@export var version: int = 2
# Initial terminal width (number of columns).
@export var width: int
# Initial terminal height (number of rows).
@export var height: int
func get_class() -> String:
return "Asciicast"
func is_class(name) -> bool:
return name == get_class() or super.is_class(name)
func _init():
step = 0.01 # Parent override.

View File

@@ -0,0 +1 @@
uid://daqhwrk6s4r7s

View File

@@ -0,0 +1,9 @@
extends Theme
func get_class() -> String:
return "XrdbTheme"
func is_class(name) -> bool:
return name == get_class() or super.is_class(name)

View File

@@ -0,0 +1 @@
uid://b5w7m5xhi0vba

View File

@@ -0,0 +1,5 @@
// SPDX-FileCopyrightText: 2024 Leroy Hopson <godot-xterm@leroy.nix.nz>
// SPDX-License-Identifier: MIT
shader_type canvas_item;
#define BACKGROUND
#include "./common.gdshaderinc"

View File

@@ -0,0 +1 @@
uid://d3dy6nhv17r6y

View File

@@ -0,0 +1,94 @@
// SPDX-FileCopyrightText: 2024 Leroy Hopson <godot-xterm@leroy.nix.nz>
// SPDX-License-Identifier: MIT
#define FLAG_INVERSE 1 << 0
#define FLAG_BLINK 1 << 1
#define FLAG_CURSOR 1 << 2
#define transparent vec4(0)
uniform int cols;
uniform int rows;
uniform vec2 size;
uniform vec2 cell_size;
uniform vec2 grid_size;
uniform sampler2D attributes;
uniform bool inverse_enabled = true;
uniform bool has_focus = false;
#ifdef BACKGROUND
uniform vec4 background_color;
uniform sampler2D background_colors;
uniform sampler2D screen_texture : hint_screen_texture, repeat_disable, filter_nearest;
#endif
#ifdef FOREGROUND
uniform float blink_off_time = 0.3;
uniform float blink_on_time = 0.6;
#endif
bool has_attribute(vec2 uv, int flag) {
int flags = int(texture(attributes, uv).r * 255.0 + 0.5);
return (flags & flag) != 0;
}
void fragment() {
// Check if we are inside the grid.
if (UV.x * size.x < grid_size.x && UV.y * size.y < grid_size.y) {
vec2 grid_uv = UV * size / cell_size;
int cell_x = int(grid_uv.x);
int cell_y = int(grid_uv.y);
vec2 sample_uv = (vec2(float(cell_x), float(cell_y)) + 0.5) / vec2(float(cols), float(rows));
vec4 color;
#ifdef BACKGROUND
color = texture(background_colors, sample_uv);
#elif defined(FOREGROUND)
color = texture(TEXTURE, UV);
#endif
bool unfocused_cursor = !has_focus && has_attribute(sample_uv, FLAG_CURSOR);
#ifdef BACKGROUND
if (has_attribute(sample_uv, FLAG_INVERSE) && inverse_enabled) {
vec4 bg_color = textureLod(screen_texture, SCREEN_UV, 0.0);
vec4 target_color;
target_color.a = color.a + (1.0 - color.a) * bg_color.a;
target_color.rgb = 1.0 / target_color.a * (color.a * color.rgb + (1.0 - color.a) * bg_color.a * bg_color.rgb);
color = vec4(1.0 - target_color.rgb, target_color.a);
}
#else
if (has_attribute(sample_uv, FLAG_INVERSE) && inverse_enabled && !unfocused_cursor) {
color.rgb = vec3(1.0 - color.rgb);
}
#endif
#ifdef FOREGROUND
if (has_attribute(sample_uv, FLAG_BLINK)) {
float blink_cycle = blink_on_time + blink_off_time;
float current_time = mod(TIME + blink_cycle, blink_cycle); // The offset ensures blink always starts at the beginning of the ON cycle.
if (current_time > blink_on_time) {
color = transparent;
}
}
#endif
#ifdef BACKGROUND
if (unfocused_cursor) {
// Draw hollow cursor when not focused.
bool isBorderX = (UV.x * size.x - float(cell_x) * cell_size.x) < 1.0 || (float(cell_x + 1) * cell_size.x - UV.x * size.x) < 1.0;
bool isBorderY = (UV.y * size.y - float(cell_y) * cell_size.y) < 1.0 || (float(cell_y + 1) * cell_size.y - UV.y * size.y) < 1.0;
if (!isBorderX && !isBorderY) {
color = transparent;
}
}
#endif
COLOR = color;
} else { // Outside the grid.
COLOR = transparent;
}
}

View File

@@ -0,0 +1 @@
uid://ceh0d3iwam1vt

View File

@@ -0,0 +1,5 @@
// SPDX-FileCopyrightText: 2024 Leroy Hopson <godot-xterm@leroy.nix.nz>
// SPDX-License-Identifier: MIT
shader_type canvas_item;
#define FOREGROUND
#include "./common.gdshaderinc"

View File

@@ -0,0 +1 @@
uid://dvtgnfmmhwteh

View File

@@ -0,0 +1,10 @@
# This is the official list of project authors for copyright purposes.
# This file is distinct from the CONTRIBUTORS.txt file.
# See the latter for an explanation.
#
# Names should be added to this file as:
# Name or Organization <email address>
JetBrains <>
Philipp Nurullin <philipp.nurullin@jetbrains.com>
Konstantin Bulenkov <kb@jetbrains.com>

View File

@@ -0,0 +1,93 @@
Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View File

@@ -0,0 +1,35 @@
[remap]
importer="font_data_dynamic"
type="FontFile"
uid="uid://c51gnbjamppg"
path="res://.godot/imported/jet_brains_mono_nl-regular-2.304.ttf-96fa438503371775be3cc744a4450fc2.fontdata"
[deps]
source_file="res://addons/godot_xterm/themes/fonts/jet_brains_mono/jet_brains_mono_nl-regular-2.304.ttf"
dest_files=["res://.godot/imported/jet_brains_mono_nl-regular-2.304.ttf-96fa438503371775be3cc744a4450fc2.fontdata"]
[params]
Rendering=null
antialiasing=1
generate_mipmaps=false
disable_embedded_bitmaps=true
multichannel_signed_distance_field=false
msdf_pixel_range=8
msdf_size=48
allow_system_fallback=true
force_autohinter=false
hinting=1
subpixel_positioning=1
keep_rounding_remainders=true
oversampling=0.0
Fallbacks=null
fallbacks=[]
Compress=null
compress=true
preload=[]
language_support={}
script_support={}
opentype_features={}

View File

@@ -0,0 +1,6 @@
[gd_resource type="FontVariation" load_steps=2 format=3 uid="uid://vmgmcu8gc6nt"]
[ext_resource type="FontFile" uid="uid://c51gnbjamppg" path="res://addons/godot_xterm/themes/fonts/jet_brains_mono/jet_brains_mono_nl-regular-2.304.ttf" id="1_8kori"]
[resource]
base_font = ExtResource("1_8kori")

View File

@@ -0,0 +1,104 @@
extends RefCounted
# Control Sequence Introducer
const CSI = "\u001b["
const CURSOR_UP = "\u001b[A"
const CURSOR_DOWN = "\u001b[B"
const CURSOR_RIGHT = "\u001b[C"
const CURSOR_LEFT = "\u001b[D"
const DEFAULT_FOREGROUND_COLOR = "\u001b[0m"
class ANSIColor:
extends Object
# Using ANSIColor constants, rather than Color will respect the
# colors of the selected terminal theme. Whereas Color will set
# the exact color specified regardless of theme.
const black = {fg = 30, panel = 40}
const red = {fg = 31, panel = 41}
const green = {fg = 32, panel = 42}
const yellow = {fg = 33, panel = 43}
const blue = {fg = 34, panel = 44}
const magenta = {fg = 35, panel = 45}
const cyan = {fg = 36, panel = 46}
const white = {fg = 37, panel = 47}
const bright_black = {fg = 90, panel = 100}
const gray = bright_black
const grey = bright_black
const bright_red = {fg = 91, panel = 101}
const bright_green = {fg = 92, panel = 102}
const bright_yellow = {fg = 93, panel = 103}
const bright_blue = {fg = 94, panel = 104}
const bright_magenta = {fg = 95, panel = 105}
const bright_cyan = {fg = 96, panel = 106}
const bright_white = {fg = 97, panel = 107}
func _init():
# "ANSIColor is an abstract class. You should only use the color constants (e.g. ANSIColor.black)."
assert(false)
var terminal
func _init(p_terminal: Control):
if p_terminal:
terminal = p_terminal
func write_string(string: String, color: Color = Color.WHITE) -> void:
if color:
var fg = "\u001b[38;2;%d;%d;%dm" % [color.r8, color.g8, color.b8]
terminal.write(fg.to_utf8_buffer())
terminal.write(string.to_utf8_buffer())
# Reset color back to default.
terminal.write("\u001b[0m".to_utf8_buffer())
# tput_* functions based on the tput command.
# See: https://man7.org/linux/man-pages/man1/tput.1.html for more info.
# Hide the cursor.
func civis():
terminal.write("%s?25l" % CSI)
# Position the cursor at the given row and col.
func cup(row: int = 0, col: int = 0) -> void:
terminal.write("\u001b[%d;%dH" % [row, col])
func setaf(color) -> void:
if color is Color:
terminal.write("\u001b[38;2;%d;%d;%dm" % [color.r8, color.g8, color.b8])
elif "fg" in color and color.fg is int:
terminal.write("\u001b[%dm" % color.fg)
else:
push_error("Invalid color: %s" % color)
func setab(color) -> void:
if color is Color:
terminal.write("\u001b[48;2;%d;%d;%dm" % [color.r8, color.g8, color.b8])
elif "panel" in color and color.panel is int:
terminal.write("\u001b[%dm" % color.panel)
else:
push_error("Invalid color: %s" % color)
func rev() -> void:
terminal.write("\u001b[7m")
func sgr0() -> void:
terminal.write("\u001b[0m")
func reset() -> void:
terminal.write("\u001bc")

View File

@@ -0,0 +1 @@
uid://dud4ii1gy5sa