This commit is contained in:
Face
2025-09-07 18:49:32 +03:00
parent b4639e80bc
commit 7220880e95
19 changed files with 1078 additions and 23 deletions

View File

@@ -451,7 +451,7 @@ func _input(event: InputEvent) -> void:
func _handle_mousemove_event(mouse_event: InputEventMouseMotion, subscription: EventSubscription) -> void:
# TODO: pass reference instead of hardcoded path
var body_container = get_node("/root/Main").website_container
var body_container = Engine.get_main_loop().current_scene.website_container
if body_container.get_parent() is MarginContainer:
body_container = body_container.get_parent()
@@ -787,6 +787,10 @@ func _handle_text_setting(operation: Dictionary):
# If the element has a DOM node, update it directly without updating text_content
var element_id = get_or_assign_element_id(element)
var dom_node = dom_parser.parse_result.dom_nodes.get(element_id, null)
if not dom_node:
dom_node = dom_parser.parse_result.dom_nodes.get(element, null)
if dom_node:
if element.tag_name == "button":
var button_node = dom_node.get_node_or_null("ButtonNode")
@@ -806,6 +810,7 @@ func _handle_text_setting(operation: Dictionary):
var text_node = get_dom_node(dom_node, "text")
if text_node:
if text_node is RichTextLabel:
element.text_content = text
StyleManager.apply_styles_to_label(text_node, dom_parser.get_element_styles_with_inheritance(element, "", []), element, dom_parser, text)
try_apply_auto_resize(text_node)
elif text_node.has_method("set_text"):

View File

@@ -29,20 +29,26 @@ func _ensure_download_progress_container():
main_node.add_child(anchor_container)
func handle_download_request(download_data: Dictionary):
print("Download requested: ", download_data)
var active_tab = main_node.get_active_tab()
if active_tab and active_tab.current_url:
download_data["current_site"] = URLUtils.extract_domain(active_tab.current_url)
else:
download_data["current_site"] = "Unknown site"
var dialog = DOWNLOAD_DIALOG.instantiate()
main_node.add_child(dialog)
var settings_node = Engine.get_main_loop().current_scene
var skip_confirmation = !settings_node.get_download_confirmation_setting()
dialog.download_confirmed.connect(_on_download_confirmed)
dialog.download_cancelled.connect(_on_download_cancelled)
dialog.show_download_dialog(download_data)
if skip_confirmation:
var filename = download_data.get("filename", "download")
var default_path = OS.get_system_dir(OS.SYSTEM_DIR_DOWNLOADS) + "/" + filename
_on_download_confirmed(download_data, default_path)
else:
var dialog = DOWNLOAD_DIALOG.instantiate()
main_node.add_child(dialog)
dialog.download_confirmed.connect(_on_download_confirmed)
dialog.download_cancelled.connect(_on_download_cancelled)
dialog.show_download_dialog(download_data)
func _on_download_confirmed(download_data: Dictionary, save_path: String):
var download_id = download_data.get("id", "")

View File

@@ -74,3 +74,14 @@ func get_download_data_array() -> Array[Dictionary]:
for entry in download_entries:
data_array.append(entry.get_download_data())
return data_array
func clear_all_downloads():
for entry in download_entries:
entry.queue_free()
download_entries.clear()
var file = FileAccess.open(save_path, FileAccess.WRITE)
if file:
file.store_string("[]")
file.close()

View File

@@ -1,8 +1,17 @@
extends RefCounted
class_name GurtProtocol
const DNS_SERVER_IP: String = "135.125.163.131"
const DNS_SERVER_PORT: int = 4878
static var DNS_SERVER_IP: String = "135.125.163.131"
static var DNS_SERVER_PORT: int = 4878
static func set_dns_server(ip_port: String):
if ":" in ip_port:
var parts = ip_port.split(":")
DNS_SERVER_IP = parts[0]
DNS_SERVER_PORT = parts[1].to_int()
else:
DNS_SERVER_IP = ip_port
DNS_SERVER_PORT = 4878
static func is_gurt_domain(url: String) -> bool:
if url.begins_with("gurt://"):

View File

@@ -1,11 +1,13 @@
extends Button
const HISTORY = preload("res://Scenes/BrowserMenus/history.tscn")
const SETTINGS = preload("res://Scenes/BrowserMenus/settings.tscn")
@onready var tab_container: TabManager = $"../../TabContainer"
@onready var main: Main = $"../../../"
var history_scene: PopupPanel = null
var settings_scene: PopupPanel = null
func _on_pressed() -> void:
%OptionsMenu.show()
@@ -42,6 +44,8 @@ func _on_options_menu_id_pressed(id: int) -> void:
show_history()
if id == 5: # downloads
show_downloads()
if id == 9: # settings
show_settings()
if id == 10: # exit
get_tree().quit()
@@ -62,3 +66,16 @@ func _on_history_closed() -> void:
func show_downloads() -> void:
main.download_manager.show_downloads_history()
func show_settings() -> void:
if settings_scene == null:
settings_scene = SETTINGS.instantiate()
main.add_child(settings_scene)
settings_scene.connect("popup_hide", _on_settings_closed)
else:
settings_scene.show()
func _on_settings_closed() -> void:
if settings_scene:
settings_scene.hide()

View File

@@ -0,0 +1,114 @@
extends Node
const SETTINGS_FILE = "user://browser_settings.json"
var settings_data = {
"startup_new_tab": true,
"startup_specific_page": false,
"startup_url": "",
"search_engine_url": "gurt://search.web?q=",
"download_confirmation": true,
"dns_url": "135.125.163.131:4878"
}
var _loaded = false
func _ready():
load_settings()
func load_settings():
if _loaded:
return
if not FileAccess.file_exists(SETTINGS_FILE):
save_settings()
_loaded = true
return
var file = FileAccess.open(SETTINGS_FILE, FileAccess.READ)
if not file:
print("Failed to open settings file")
_loaded = true
return
var json_text = file.get_as_text()
file.close()
var json = JSON.new()
var parse_result = json.parse(json_text)
if parse_result != OK:
print("Failed to parse settings JSON")
_loaded = true
return
var loaded_data = json.data
if loaded_data is Dictionary:
# Merge loaded settings with defaults
for key in loaded_data:
if key in settings_data:
settings_data[key] = loaded_data[key]
_loaded = true
print("Settings loaded: ", settings_data)
func save_settings():
var file = FileAccess.open(SETTINGS_FILE, FileAccess.WRITE)
if not file:
print("Failed to open settings file for writing")
return
var json_text = JSON.stringify(settings_data)
file.store_string(json_text)
file.close()
print("Settings saved: ", settings_data)
func get_download_confirmation() -> bool:
return settings_data.download_confirmation
func get_search_engine_url() -> String:
return settings_data.search_engine_url
func get_dns_url() -> String:
return settings_data.dns_url
func get_startup_behavior() -> Dictionary:
return {
"new_tab": settings_data.startup_new_tab,
"specific_page": settings_data.startup_specific_page,
"url": settings_data.startup_url
}
func set_download_confirmation(value: bool):
settings_data.download_confirmation = value
save_settings()
func set_search_engine_url(value: String):
settings_data.search_engine_url = value
save_settings()
func set_dns_url(value: String):
settings_data.dns_url = value
save_settings()
# Update GurtProtocol immediately
GurtProtocol.set_dns_server(value)
func set_startup_new_tab(value: bool):
settings_data.startup_new_tab = value
if value:
settings_data.startup_specific_page = false
save_settings()
func set_startup_specific_page(value: bool):
settings_data.startup_specific_page = value
if value:
settings_data.startup_new_tab = false
save_settings()
func set_startup_url(value: String):
settings_data.startup_url = value
save_settings()
func get_setting(key: String, default_value = null):
if key in settings_data:
return settings_data[key]
return default_value

View File

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

View File

@@ -229,8 +229,11 @@ func create_tab() -> void:
trigger_init_scene(tab)
# WARNING: temporary
main.render()
var startup_behavior = main.get_startup_behavior()
if startup_behavior.specific_page and not startup_behavior.url.is_empty():
main._on_search_submitted(startup_behavior.url, true)
else:
main.render()
func _input(_event: InputEvent) -> void:
if Input.is_action_just_pressed("NewTab"):

View File

@@ -0,0 +1,143 @@
extends PopupPanel
const SETTINGS_FILE = "user://browser_settings.json"
# Tab references
@onready var tab_general: Button = $HSplitContainer/Sidebar/VBoxContainer/TabGeneral
@onready var tab_privacy: Button = $HSplitContainer/Sidebar/VBoxContainer/TabPrivacy
@onready var tab_downloads: Button = $HSplitContainer/Sidebar/VBoxContainer/TabDownloads
@onready var tab_advanced: Button = $HSplitContainer/Sidebar/VBoxContainer/TabAdvanced
@onready var tab_about: Button = $HSplitContainer/Sidebar/VBoxContainer/TabAbout
# Panel references
@onready var general_panel: VBoxContainer = $HSplitContainer/Content/ScrollContainer/ContentStack/GeneralPanel
@onready var privacy_panel: VBoxContainer = $HSplitContainer/Content/ScrollContainer/ContentStack/PrivacyPanel
@onready var downloads_panel: VBoxContainer = $HSplitContainer/Content/ScrollContainer/ContentStack/DownloadsPanel
@onready var advanced_panel: VBoxContainer = $HSplitContainer/Content/ScrollContainer/ContentStack/AdvancedPanel
@onready var about_panel: VBoxContainer = $HSplitContainer/Content/ScrollContainer/ContentStack/AboutPanel
# Settings controls - General
@onready var startup_new_tab: CheckBox = $HSplitContainer/Content/ScrollContainer/ContentStack/GeneralPanel/StartupSection/VBoxContainer/StartupNewTab
@onready var startup_specific_page: CheckBox = $HSplitContainer/Content/ScrollContainer/ContentStack/GeneralPanel/StartupSection/VBoxContainer/StartupSpecificPage
@onready var startup_url_container: HBoxContainer = $HSplitContainer/Content/ScrollContainer/ContentStack/GeneralPanel/StartupSection/VBoxContainer/StartupURLContainer
@onready var startup_url_input: LineEdit = $HSplitContainer/Content/ScrollContainer/ContentStack/GeneralPanel/StartupSection/VBoxContainer/StartupURLContainer/URLInput
@onready var search_input: LineEdit = $HSplitContainer/Content/ScrollContainer/ContentStack/GeneralPanel/SearchEngineSection/VBoxContainer/HBoxContainer/SearchInput
# Settings controls - Privacy
@onready var clear_history_button: Button = $HSplitContainer/Content/ScrollContainer/ContentStack/PrivacyPanel/ClearDataSection/VBoxContainer/ButtonsContainer/ClearHistoryButton
@onready var clear_cookies_button: Button = $HSplitContainer/Content/ScrollContainer/ContentStack/PrivacyPanel/ClearDataSection/VBoxContainer/ButtonsContainer/ClearCookiesButton
@onready var clear_downloads_button: Button = $HSplitContainer/Content/ScrollContainer/ContentStack/PrivacyPanel/ClearDataSection/VBoxContainer/ButtonsContainer/ClearDownloadsButton
# Settings controls - Downloads
@onready var download_confirm_checkbox: CheckBox = $HSplitContainer/Content/ScrollContainer/ContentStack/DownloadsPanel/DownloadConfirmSection/VBoxContainer/ConfirmCheckBox
# Settings controls - Advanced
@onready var dns_input: LineEdit = $HSplitContainer/Content/ScrollContainer/ContentStack/AdvancedPanel/DNSSection/VBoxContainer/HBoxContainer/DNSInput
# Settings are now managed by SettingsManager singleton
var tab_buttons = []
var content_panels = []
func _ready():
tab_buttons = [tab_general, tab_privacy, tab_downloads, tab_advanced, tab_about]
content_panels = [general_panel, privacy_panel, downloads_panel, advanced_panel, about_panel]
tab_general.toggled.connect(_on_tab_toggled.bind("general"))
tab_privacy.toggled.connect(_on_tab_toggled.bind("privacy"))
tab_downloads.toggled.connect(_on_tab_toggled.bind("downloads"))
tab_advanced.toggled.connect(_on_tab_toggled.bind("advanced"))
tab_about.toggled.connect(_on_tab_toggled.bind("about"))
startup_new_tab.toggled.connect(_on_startup_new_tab_toggled)
startup_specific_page.toggled.connect(_on_startup_specific_page_toggled)
startup_url_input.text_changed.connect(_on_startup_url_changed)
search_input.text_changed.connect(_on_search_engine_changed)
download_confirm_checkbox.toggled.connect(_on_download_confirmation_changed)
dns_input.text_changed.connect(_on_dns_changed)
clear_history_button.pressed.connect(_on_clear_history_pressed)
clear_cookies_button.pressed.connect(_on_clear_cookies_pressed)
clear_downloads_button.pressed.connect(_on_clear_downloads_pressed)
apply_settings_to_ui()
_on_tab_toggled(true, "general")
func _on_tab_toggled(pressed: bool, tab_name: String):
if not pressed:
return
_update_tab_styles(tab_name)
for i in range(content_panels.size()):
var panel = content_panels[i]
match tab_name:
"general":
panel.visible = (i == 0)
"privacy":
panel.visible = (i == 1)
"downloads":
panel.visible = (i == 2)
"advanced":
panel.visible = (i == 3)
"about":
panel.visible = (i == 4)
func _update_tab_styles(selected_tab: String):
for i in range(tab_buttons.size()):
var button = tab_buttons[i]
var tab_names = ["general", "privacy", "downloads", "advanced", "about"]
var is_selected = (tab_names[i] == selected_tab)
if is_selected:
button.modulate = Color.WHITE
else:
button.modulate = Color(0.8, 0.8, 0.8)
func _on_startup_new_tab_toggled(pressed: bool):
if pressed:
SettingsManager.set_startup_new_tab(true)
startup_url_container.visible = false
func _on_startup_specific_page_toggled(pressed: bool):
if pressed:
SettingsManager.set_startup_specific_page(true)
startup_url_container.visible = true
func _on_startup_url_changed(new_url: String):
SettingsManager.set_startup_url(new_url)
func _on_search_engine_changed(new_url: String):
SettingsManager.set_search_engine_url(new_url)
func _on_download_confirmation_changed(enabled: bool):
SettingsManager.set_download_confirmation(enabled)
func _on_dns_changed(new_dns: String):
SettingsManager.set_dns_url(new_dns)
func _on_clear_history_pressed():
BrowserHistory.clear_all()
func _on_clear_cookies_pressed():
LuaCrumbsUtils.clear_all_crumbs()
func _on_clear_downloads_pressed():
var main = Engine.get_main_loop().current_scene
if main.download_manager.downloads_history_ui:
main.download_manager.downloads_history_ui.clear_all_downloads()
else:
main.download_manager._ensure_downloads_history_ui()
main.download_manager.downloads_history_ui.clear_all_downloads()
func apply_settings_to_ui():
var startup_behavior = SettingsManager.get_startup_behavior()
startup_new_tab.button_pressed = startup_behavior.new_tab
startup_specific_page.button_pressed = startup_behavior.specific_page
startup_url_container.visible = startup_behavior.specific_page
startup_url_input.text = startup_behavior.url
search_input.text = SettingsManager.get_search_engine_url()
download_confirm_checkbox.button_pressed = SettingsManager.get_download_confirmation()
dns_input.text = SettingsManager.get_dns_url()

View File

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

View File

@@ -38,10 +38,20 @@ input[type="date"] { w-28 text-[16px] bg-[#1b1b1b] rounded-md text-white hover:b
var HTML_CONTENT = """
<head>
<title>New tab</title>
<script>
local items = {"Hi","Hello","Salut","Bonjour","Hola","Ciao","Hallo","Hej","Hei","Ola","Privet","Zdravstvuyte","Konnichiwa","Ni hao","Annyeonghaseyo","Merhaba","Selam","Habari","Shalom","Namaste","Marhaba","Geia","Sawasdee","Selamat","Halo","Kumusta","Sawubona","Jambo","Aloha","Goddag","Tere","Moikka","Sveiki"}
local h = gurt.select(".target")
local res = items[math.random(#items)] .. "!"
h.text = res
</script>
</head>
<body>
<p>Welcome to Flumi Browser!</p>
<img src="https://httpbin.org/image/png" alt="Test image" />
<p>This page includes a test image to verify network functionality.</p>
<body style="bg-[#323949] text-white font-sans">
<div style="flex flex-col items-center justify-center w-full mt-12">
<h1 style="target text-8xl font-bold mb-4 text-[#4a9eff] font-serif font-italic">Hello!</h1>
<p style="text-lg mb-8 text-[#cccccc]">Start browsing by typing in the omnibar.</p>
<p style="mb-2">Happy GURT:// exploration!</p>
</div>
</body>
""".to_utf8_buffer()

View File

@@ -13,6 +13,12 @@ func init(element, parser: HTMLParser) -> void:
size_flags_vertical = Control.SIZE_SHRINK_BEGIN
mouse_filter = Control.MOUSE_FILTER_PASS
var element_id = element.get_attribute("id")
if element_id.is_empty():
element.set_attribute("id", unique_id)
element_id = unique_id
parser.register_dom_node(element, self)
if get_child_count() > 0:
return
@@ -62,7 +68,6 @@ func create_styled_label(text: String, element, parser: HTMLParser) -> RichTextL
add_child(label)
parser.register_dom_node(element, label)
var styles = parser.get_element_styles_with_inheritance(element, "", [])
StyleManager.apply_styles_to_label(label, styles, element, parser, text)
@@ -140,6 +145,9 @@ func set_text(new_text: String) -> void:
remove_child(child)
child.queue_free()
if _element:
_element.text_content = new_text
if _element and _parser:
create_styled_label(new_text, _element, _parser)
else:
@@ -172,8 +180,6 @@ func create_label(text: String) -> RichTextLabel:
add_child(label)
if _element and _parser:
_parser.register_dom_node(_element, label)
call_deferred("_apply_auto_resize_to_label", label)
return label

View File

@@ -254,3 +254,15 @@ static func delete_crumb(domain: String, name: String) -> bool:
all_crumbs.erase(name)
save_all_crumbs(domain, all_crumbs)
return existed
static func clear_all_crumbs():
if DirAccess.dir_exists_absolute(CRUMBS_DIR_PATH):
var dir = DirAccess.open(CRUMBS_DIR_PATH)
if dir:
dir.list_dir_begin()
var file_name = dir.get_next()
while file_name != "":
if file_name.ends_with(".json"):
dir.remove(file_name)
file_name = dir.get_next()
dir.list_dir_end()

View File

@@ -507,7 +507,7 @@ static func is_same_element_visual_node(node1: Node, node2: Node) -> bool:
return false
static func render_new_element_at_position(element: HTMLParser.HTMLElement, parent_node: Node, position: int, dom_parser: HTMLParser) -> void:
var main_scene = Engine.get_main_loop().current_scene.get_node("/root/Main")
var main_scene = Engine.get_main_loop().current_scene.Engine.get_main_loop().current_scene
if not main_scene:
return
@@ -529,7 +529,7 @@ static func render_new_element_at_position(element: HTMLParser.HTMLElement, pare
container_node.move_child(element_node, position)
static func render_new_element_by_reference(element: HTMLParser.HTMLElement, parent_node: Node, reference_element_id: String, insert_before: bool, dom_parser: HTMLParser) -> void:
var main_scene = Engine.get_main_loop().current_scene.get_node("/root/Main")
var main_scene = Engine.get_main_loop().current_scene.Engine.get_main_loop().current_scene
if not main_scene:
return

View File

@@ -73,6 +73,7 @@ func _ready():
call_deferred("render")
call_deferred("update_navigation_buttons")
call_deferred("_handle_startup_behavior")
func _input(_event: InputEvent) -> void:
if Input.is_action_just_pressed("DevTools"):
@@ -114,7 +115,15 @@ func _on_search_submitted(url: String, add_to_history: bool = true) -> void:
await fetch_gurt_content_async(gurt_url, tab, url, add_to_history)
else:
print("Non-GURT URL entered: ", url)
print("Non-GURT URL entered, using search engine: ", url)
if url.begins_with("http://") or url.begins_with("https://"):
# It's already a web URL, open in system browser
OS.shell_open(url)
else:
var search_engine_url = get_search_engine_url()
var search_url = search_engine_url + url.uri_encode()
_on_search_submitted(search_url, add_to_history)
func fetch_gurt_content_async(gurt_url: String, tab: Tab, original_url: String, add_to_history: bool = true) -> void:
main_navigation_request = NetworkManager.start_request(gurt_url, "GET", false)
@@ -817,3 +826,18 @@ func update_navigation_buttons() -> void:
else:
back_button.disabled = true
forward_button.disabled = true
func get_download_confirmation_setting() -> bool:
return SettingsManager.get_download_confirmation()
func get_search_engine_url() -> String:
return SettingsManager.get_search_engine_url()
func get_startup_behavior() -> Dictionary:
return SettingsManager.get_startup_behavior()
func _handle_startup_behavior():
var startup_behavior = get_startup_behavior()
if startup_behavior.specific_page and not startup_behavior.url.is_empty():
_on_search_submitted(startup_behavior.url, true)