GURT protocol (lib, cli, gdextension, Flumi integration)
This commit is contained in:
@@ -13,14 +13,14 @@ func _resort() -> void:
|
||||
size_flags_horizontal = Control.SIZE_FILL
|
||||
else:
|
||||
if not has_meta("size_flags_set_by_style_manager"):
|
||||
size_flags_horizontal = Control.SIZE_SHRINK_CENTER
|
||||
size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
|
||||
|
||||
# Check if we should fill vertically (for h-full)
|
||||
if has_meta("should_fill_vertical"):
|
||||
size_flags_vertical = Control.SIZE_FILL
|
||||
else:
|
||||
if not has_meta("size_flags_set_by_style_manager"):
|
||||
size_flags_vertical = Control.SIZE_SHRINK_CENTER
|
||||
size_flags_vertical = Control.SIZE_SHRINK_BEGIN
|
||||
|
||||
if debug_draw:
|
||||
_draw_rects.clear()
|
||||
|
||||
@@ -106,7 +106,6 @@ func handle_style_element(style_element: HTMLElement) -> void:
|
||||
var src = style_element.get_attribute("src")
|
||||
if src.length() > 0:
|
||||
# TODO: Handle external CSS loading when Network module is available
|
||||
print("External CSS not yet supported: " + src)
|
||||
return
|
||||
|
||||
# Handle inline CSS - we'll get the text content when parsing is complete
|
||||
@@ -119,21 +118,19 @@ func process_styles() -> void:
|
||||
if not parse_result.css_parser:
|
||||
return
|
||||
|
||||
# Collect all style element content
|
||||
var css_content = Constants.DEFAULT_CSS
|
||||
var style_elements = find_all("style")
|
||||
for style_element in style_elements:
|
||||
if style_element.get_attribute("src").is_empty():
|
||||
css_content += style_element.text_content + "\n"
|
||||
print("Processing CSS: ", css_content)
|
||||
# Parse CSS if we have any
|
||||
|
||||
if css_content.length() > 0:
|
||||
parse_result.css_parser.css_text = css_content
|
||||
parse_result.css_parser.parse()
|
||||
for child: CSSParser.CSSRule in parse_result.css_parser.stylesheet.rules:
|
||||
print("INFO: for selector \"%s\" we have props: %s" % [child.selector, child.properties])
|
||||
|
||||
func get_element_styles_with_inheritance(element: HTMLElement, event: String = "", visited_elements: Array = []) -> Dictionary:
|
||||
if !parse_result.css_parser:
|
||||
return {}
|
||||
# Prevent infinite recursion
|
||||
if element in visited_elements:
|
||||
return {}
|
||||
@@ -335,7 +332,7 @@ func process_fonts() -> void:
|
||||
var weight = font_element.get_attribute("weight", "400")
|
||||
|
||||
if name_str and src:
|
||||
FontManager.register_font(name, src, weight)
|
||||
FontManager.register_font(name_str, src, weight)
|
||||
|
||||
func get_meta_content(name_: String) -> String:
|
||||
var meta_elements = find_all("meta", "name")
|
||||
@@ -366,9 +363,8 @@ func process_scripts(lua_api: LuaAPI, lua_vm) -> void:
|
||||
|
||||
if not src.is_empty():
|
||||
# TODO: add support for external Lua script
|
||||
print("External script found: ", src)
|
||||
pass
|
||||
elif not inline_code.is_empty():
|
||||
print("Executing inline Lua script")
|
||||
lua_api.execute_lua_script(inline_code, lua_vm)
|
||||
|
||||
func get_all_stylesheets() -> Array[String]:
|
||||
|
||||
@@ -6,7 +6,7 @@ const SECONDARY_COLOR = Color(43/255.0, 43/255.0, 43/255.0, 1)
|
||||
const HOVER_COLOR = Color(0, 0, 0, 1)
|
||||
|
||||
const DEFAULT_CSS = """
|
||||
body { text-base text-[#000000] text-left }
|
||||
body { text-base text-[#000000] text-left bg-white }
|
||||
h1 { text-5xl font-bold }
|
||||
h2 { text-4xl font-bold }
|
||||
h3 { text-3xl font-bold }
|
||||
|
||||
@@ -7,32 +7,51 @@ static func is_gurt_domain(url: String) -> bool:
|
||||
if url.begins_with("gurt://"):
|
||||
return true
|
||||
|
||||
var parts = url.split(".")
|
||||
return parts.size() == 2 and not url.contains("://")
|
||||
if not url.contains("://"):
|
||||
var parts = url.split(".")
|
||||
return parts.size() == 2
|
||||
|
||||
return false
|
||||
|
||||
static func parse_gurt_domain(url: String) -> Dictionary:
|
||||
print("Parsing URL: ", url)
|
||||
|
||||
var domain_part = url
|
||||
|
||||
if url.begins_with("gurt://"):
|
||||
domain_part = url.substr(7) # Remove "gurt://"
|
||||
domain_part = url.substr(7)
|
||||
|
||||
if domain_part.contains(":") or domain_part.begins_with("127.0.0.1") or domain_part.begins_with("localhost") or is_ip_address(domain_part):
|
||||
return {
|
||||
"direct_address": domain_part,
|
||||
"display_url": domain_part,
|
||||
"is_direct": true
|
||||
}
|
||||
|
||||
var parts = domain_part.split(".")
|
||||
if parts.size() != 2:
|
||||
print("Invalid domain format: ", domain_part)
|
||||
return {}
|
||||
|
||||
print("Parsed domain - name: ", parts[0], ", tld: ", parts[1])
|
||||
return {
|
||||
"name": parts[0],
|
||||
"tld": parts[1],
|
||||
"display_url": domain_part
|
||||
"display_url": domain_part,
|
||||
"is_direct": false
|
||||
}
|
||||
|
||||
static func fetch_domain_info(name: String, tld: String) -> Dictionary:
|
||||
print("Fetching domain info for: ", name, ".", tld)
|
||||
static func is_ip_address(address: String) -> bool:
|
||||
var parts = address.split(".")
|
||||
if parts.size() != 4:
|
||||
return false
|
||||
|
||||
for part in parts:
|
||||
if not part.is_valid_int():
|
||||
return false
|
||||
var num = part.to_int()
|
||||
if num < 0 or num > 255:
|
||||
return false
|
||||
|
||||
return true
|
||||
|
||||
static func fetch_domain_info(name: String, tld: String) -> Dictionary:
|
||||
var http_request = HTTPRequest.new()
|
||||
var tree = Engine.get_main_loop()
|
||||
tree.current_scene.add_child(http_request)
|
||||
@@ -53,15 +72,11 @@ static func fetch_domain_info(name: String, tld: String) -> Dictionary:
|
||||
http_request.queue_free()
|
||||
|
||||
if response[1] == 0 and response[3].size() == 0:
|
||||
print("DNS API request timed out")
|
||||
return {"error": "DNS server is not responding"}
|
||||
|
||||
var http_code = response[1]
|
||||
var body = response[3]
|
||||
|
||||
print("DNS API response code: ", http_code)
|
||||
print("DNS API response body: ", body.get_string_from_utf8())
|
||||
|
||||
if http_code != 200:
|
||||
return {"error": "Domain not found or not approved"}
|
||||
|
||||
@@ -69,65 +84,124 @@ static func fetch_domain_info(name: String, tld: String) -> Dictionary:
|
||||
var parse_result = json.parse(body.get_string_from_utf8())
|
||||
|
||||
if parse_result != OK:
|
||||
print("JSON parse error: ", parse_result)
|
||||
return {"error": "Invalid JSON response from DNS server"}
|
||||
|
||||
print("Domain info retrieved: ", json.data)
|
||||
return json.data
|
||||
|
||||
static func fetch_index_html(ip: String) -> String:
|
||||
print("Fetching index.html from IP: ", ip)
|
||||
static func fetch_content_via_gurt(ip: String, path: String = "/") -> Dictionary:
|
||||
var client = GurtProtocolClient.new()
|
||||
|
||||
var http_request = HTTPRequest.new()
|
||||
var tree = Engine.get_main_loop()
|
||||
tree.current_scene.add_child(http_request)
|
||||
if not client.create_client(30):
|
||||
return {"error": "Failed to create GURT client"}
|
||||
|
||||
http_request.timeout = 5.0
|
||||
var gurt_url = "gurt://" + ip + ":4878" + path
|
||||
|
||||
var url = "http://" + ip + "/index.html"
|
||||
print("Fetching from URL: ", url)
|
||||
var response = client.request(gurt_url, {"method": "GET"})
|
||||
|
||||
var error = http_request.request(url)
|
||||
client.disconnect()
|
||||
|
||||
if error != OK:
|
||||
print("HTTP request to IP failed with error: ", error)
|
||||
http_request.queue_free()
|
||||
return ""
|
||||
if not response:
|
||||
return {"error": "No response from GURT server"}
|
||||
|
||||
var response = await http_request.request_completed
|
||||
http_request.queue_free()
|
||||
if not response.is_success:
|
||||
var error_msg = "Server returned status " + str(response.status_code) + ": " + response.status_message
|
||||
return {"error": error_msg}
|
||||
|
||||
if response[1] == 0 and response[3].size() == 0:
|
||||
print("Index.html request timed out")
|
||||
return ""
|
||||
|
||||
var http_code = response[1]
|
||||
var body = response[3]
|
||||
|
||||
print("IP response code: ", http_code)
|
||||
|
||||
if http_code != 200:
|
||||
print("Failed to fetch index.html, HTTP code: ", http_code)
|
||||
return ""
|
||||
|
||||
var html_content = body.get_string_from_utf8()
|
||||
print("Successfully fetched HTML content (", html_content.length(), " characters)")
|
||||
return html_content
|
||||
var content = response.body
|
||||
return {"content": content, "headers": response.headers}
|
||||
|
||||
static func handle_gurt_domain(url: String) -> Dictionary:
|
||||
print("Handling GURT domain: ", url)
|
||||
static func fetch_content_via_gurt_direct(address: String, path: String = "/") -> Dictionary:
|
||||
var shared_result = {"finished": false}
|
||||
var thread = Thread.new()
|
||||
var mutex = Mutex.new()
|
||||
|
||||
var thread_func = func():
|
||||
var local_result = {}
|
||||
var client = GurtProtocolClient.new()
|
||||
|
||||
if not client.create_client(10):
|
||||
local_result = {"error": "Failed to create GURT client"}
|
||||
else:
|
||||
var gurt_url: String
|
||||
if address.contains(":"):
|
||||
gurt_url = "gurt://" + address + path
|
||||
else:
|
||||
gurt_url = "gurt://" + address + ":4878" + path
|
||||
|
||||
var response = client.request(gurt_url, {"method": "GET"})
|
||||
|
||||
client.disconnect()
|
||||
|
||||
if not response:
|
||||
local_result = {"error": "No response from GURT server"}
|
||||
else:
|
||||
var content = response.body
|
||||
|
||||
if not response.is_success:
|
||||
var error_msg = "Server returned status " + str(response.status_code) + ": " + response.status_message
|
||||
local_result = {"error": error_msg, "content": content, "headers": response.headers}
|
||||
else:
|
||||
local_result = {"content": content, "headers": response.headers}
|
||||
|
||||
mutex.lock()
|
||||
shared_result.clear()
|
||||
for key in local_result:
|
||||
shared_result[key] = local_result[key]
|
||||
shared_result["finished"] = true
|
||||
mutex.unlock()
|
||||
|
||||
thread.start(thread_func)
|
||||
|
||||
var finished = false
|
||||
while not finished:
|
||||
await Engine.get_main_loop().process_frame
|
||||
OS.delay_msec(10)
|
||||
|
||||
mutex.lock()
|
||||
finished = shared_result.get("finished", false)
|
||||
mutex.unlock()
|
||||
|
||||
thread.wait_to_finish()
|
||||
|
||||
mutex.lock()
|
||||
var final_result = {}
|
||||
for key in shared_result:
|
||||
if key != "finished":
|
||||
final_result[key] = shared_result[key]
|
||||
mutex.unlock()
|
||||
|
||||
return final_result
|
||||
|
||||
static func handle_gurt_domain(url: String) -> Dictionary:
|
||||
var parsed = parse_gurt_domain(url)
|
||||
if parsed.is_empty():
|
||||
return {"error": "Invalid domain format. Use: domain.tld", "html": create_error_page("Invalid domain format. Use: domain.tld")}
|
||||
return {"error": "Invalid domain format. Use: domain.tld or IP:port", "html": create_error_page("Invalid domain format. Use: domain.tld or IP:port")}
|
||||
|
||||
var domain_info = await fetch_domain_info(parsed.name, parsed.tld)
|
||||
if domain_info.has("error"):
|
||||
return {"error": domain_info.error, "html": create_error_page(domain_info.error)}
|
||||
var target_address: String
|
||||
var path = "/"
|
||||
|
||||
var html_content = await fetch_index_html(domain_info.ip)
|
||||
if parsed.get("is_direct", false):
|
||||
target_address = parsed.direct_address
|
||||
else:
|
||||
var domain_info = await fetch_domain_info(parsed.name, parsed.tld)
|
||||
if domain_info.has("error"):
|
||||
return {"error": domain_info.error, "html": create_error_page(domain_info.error)}
|
||||
target_address = domain_info.ip
|
||||
|
||||
var content_result = await fetch_content_via_gurt_direct(target_address, path)
|
||||
if content_result.has("error"):
|
||||
var error_msg = "Failed to fetch content from " + target_address + " via GURT protocol - " + content_result.error
|
||||
if content_result.has("content") and not content_result.content.is_empty():
|
||||
return {"html": content_result.content, "display_url": parsed.display_url}
|
||||
return {"error": error_msg, "html": create_error_page(error_msg)}
|
||||
|
||||
if not content_result.has("content"):
|
||||
var error_msg = "No content received from " + target_address
|
||||
return {"error": error_msg, "html": create_error_page(error_msg)}
|
||||
|
||||
var html_content = content_result.content
|
||||
if html_content.is_empty():
|
||||
var error_msg = "Failed to fetch index.html from " + domain_info.ip
|
||||
var error_msg = "Empty content received from " + target_address
|
||||
return {"error": error_msg, "html": create_error_page(error_msg)}
|
||||
|
||||
return {"html": html_content, "display_url": parsed.display_url}
|
||||
@@ -137,7 +211,7 @@ static func get_error_type(error_message: String) -> Dictionary:
|
||||
return {"code": "ERR_NAME_NOT_RESOLVED", "title": "This site can't be reached", "icon": "🌐"}
|
||||
elif "timeout" in error_message.to_lower() or "timed out" in error_message.to_lower():
|
||||
return {"code": "ERR_CONNECTION_TIMED_OUT", "title": "This site can't be reached", "icon": "⏰"}
|
||||
elif "Failed to fetch" in error_message or "HTTP request failed" in error_message:
|
||||
elif "Failed to fetch" in error_message or "No response" in error_message:
|
||||
return {"code": "ERR_CONNECTION_REFUSED", "title": "This site can't be reached", "icon": "🚫"}
|
||||
elif "Invalid domain format" in error_message:
|
||||
return {"code": "ERR_INVALID_URL", "title": "This page isn't working", "icon": "⚠️"}
|
||||
|
||||
@@ -45,7 +45,6 @@ func apply_button_styles(element: HTMLParser.HTMLElement, parser: HTMLParser) ->
|
||||
|
||||
if styles.has("font-size"):
|
||||
var font_size = int(styles["font-size"])
|
||||
print("SETTING FONT SIZE: ", font_size, " FOR BUTTON NAME: ", element.tag_name)
|
||||
button_node.add_theme_font_size_override("font_size", font_size)
|
||||
|
||||
# Apply text color with state-dependent colors
|
||||
|
||||
@@ -32,5 +32,4 @@ static func get_user_agent() -> String:
|
||||
godot_version.minor,
|
||||
godot_version.patch
|
||||
]
|
||||
print(user_agent)
|
||||
return user_agent
|
||||
|
||||
@@ -87,7 +87,7 @@ func _on_search_submitted(url: String) -> void:
|
||||
tab.stop_loading()
|
||||
tab.set_icon(GLOBE_ICON)
|
||||
|
||||
var html_bytes = result.html.to_utf8_buffer()
|
||||
var html_bytes = result.html
|
||||
render_content(html_bytes)
|
||||
|
||||
if result.has("display_url"):
|
||||
@@ -105,7 +105,6 @@ func _on_search_focus_exited() -> void:
|
||||
if not current_domain.is_empty():
|
||||
search_bar.text = current_domain
|
||||
|
||||
|
||||
func render() -> void:
|
||||
render_content(Constants.HTML_CONTENT)
|
||||
|
||||
|
||||
BIN
flumi/addons/gurt-protocol/bin/windows/gurt_godot.dll
Normal file
BIN
flumi/addons/gurt-protocol/bin/windows/gurt_godot.dll
Normal file
Binary file not shown.
13
flumi/addons/gurt-protocol/gurt_godot.gdextension
Normal file
13
flumi/addons/gurt-protocol/gurt_godot.gdextension
Normal file
@@ -0,0 +1,13 @@
|
||||
[configuration]
|
||||
|
||||
entry_symbol = "gdext_rust_init"
|
||||
compatibility_minimum = 4.1
|
||||
|
||||
[libraries]
|
||||
|
||||
macos.debug = "res://addons/gurt-protocol/bin/macos/libgurt_godot.dylib"
|
||||
macos.release = "res://addons/gurt-protocol/bin/macos/libgurt_godot.dylib"
|
||||
windows.debug.x86_64 = "res://addons/gurt-protocol/bin/windows/gurt_godot.dll"
|
||||
windows.release.x86_64 = "res://addons/gurt-protocol/bin/windows/gurt_godot.dll"
|
||||
linux.debug.x86_64 = "res://addons/gurt-protocol/bin/linux/libgurt_godot.so"
|
||||
linux.release.x86_64 = "res://addons/gurt-protocol/bin/linux/libgurt_godot.so"
|
||||
1
flumi/addons/gurt-protocol/gurt_godot.gdextension.uid
Normal file
1
flumi/addons/gurt-protocol/gurt_godot.gdextension.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dgbwo0xlp5gya
|
||||
7
flumi/addons/gurt-protocol/plugin.cfg
Normal file
7
flumi/addons/gurt-protocol/plugin.cfg
Normal file
@@ -0,0 +1,7 @@
|
||||
[plugin]
|
||||
|
||||
name="GURT Protocol"
|
||||
description="HTTP-like networking extension for Godot games using the GURT protocol"
|
||||
author="FaceDev"
|
||||
version="0.1.0"
|
||||
script="plugin.gd"
|
||||
8
flumi/addons/gurt-protocol/plugin.gd
Normal file
8
flumi/addons/gurt-protocol/plugin.gd
Normal file
@@ -0,0 +1,8 @@
|
||||
@tool
|
||||
extends EditorPlugin
|
||||
|
||||
func _enter_tree():
|
||||
print("GURT Protocol plugin enabled")
|
||||
|
||||
func _exit_tree():
|
||||
print("GURT Protocol plugin disabled")
|
||||
1
flumi/addons/gurt-protocol/plugin.gd.uid
Normal file
1
flumi/addons/gurt-protocol/plugin.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://tt2hh4j8txne
|
||||
@@ -29,7 +29,7 @@ window/stretch/aspect="ignore"
|
||||
|
||||
[editor_plugins]
|
||||
|
||||
enabled=PackedStringArray("res://addons/DatePicker/plugin.cfg", "res://addons/SmoothScroll/plugin.cfg", "res://addons/godot-flexbox/plugin.cfg")
|
||||
enabled=PackedStringArray("res://addons/DatePicker/plugin.cfg", "res://addons/SmoothScroll/plugin.cfg", "res://addons/godot-flexbox/plugin.cfg", "res://addons/gurt-protocol/plugin.cfg")
|
||||
|
||||
[file_customization]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user