Son aktivite 1786000335

ZZ's Avatar ZZ bu gisti düzenledi 1786000335. Düzenlemeye git

1 file changed, 0 insertions, 0 deletions

clipboard_engine.md yeniden adlandırıldı clipboard_engine.gd

Dosya değişiklik yapılmadan yeniden adlandırıldı

ZZ's Avatar ZZ bu gisti düzenledi 1786000326. Düzenlemeye git

3 files changed, 520 insertions

clipboard_engine.md(dosya oluşturuldu)

@@ -0,0 +1,228 @@
1 + extends Node
2 +
3 + var synchronizer: MultiplayerSynchronizer # Declare variable to keep reference
4 +
5 + # Signal emitted when network status or clipboard text changes
6 + signal status_changed(text: String)
7 +
8 + var server_ip: String = "127.0.0.1"
9 + var port_number: int = 8443
10 + var secret_key: String = "ChangeMe123!"
11 + var clipboard_text: String = "Waiting for connection..."
12 +
13 + var is_connected_to_network: bool = false
14 + var is_server_mode: bool = false
15 +
16 + # Reconnection and Rate-Limiting Tracker variables
17 + var is_reconnecting: bool = false
18 + var retry_timer: float = 0.0
19 + const RETRY_INTERVAL: float = 10.0
20 + var last_log_time: float = 0.0
21 + const LOG_LIMIT_INTERVAL: float = 10.0
22 +
23 + const CONFIG_PATH = "user://network_settings.cfg"
24 +
25 + func _ready() -> void:
26 + synchronizer = MultiplayerSynchronizer.new()
27 + #synchronizer.update_rate = 30.0
28 + add_child(synchronizer)
29 + load_network_config()
30 +
31 + # Connect Godot's network lifecycle signals
32 + multiplayer.connected_to_server.connect(_on_connected_ok)
33 + multiplayer.connection_failed.connect(_on_connection_failed)
34 + multiplayer.server_disconnected.connect(_on_disconnected)
35 + multiplayer.peer_connected.connect(_on_peer_connected)
36 + multiplayer.peer_disconnected.connect(_on_peer_disconnected)
37 +
38 + update_status("DISCONNECTED | Secure Clip: Empty")
39 +
40 + func _process(delta: float) -> void:
41 + # 1. Manage Clipboard Synchronization
42 + var current_sys_clip = DisplayServer.clipboard_get()
43 + if current_sys_clip != clipboard_text:
44 + if multiplayer.is_server() or current_sys_clip != "":
45 + var encrypted_payload = encrypt_string(current_sys_clip)
46 + rpc("receive_secure_clipboard", encrypted_payload)
47 +
48 + # 2. Manage Automatic Client Reconnection Loop
49 + if is_reconnecting and not is_server_mode:
50 + retry_timer += delta
51 + if retry_timer >= RETRY_INTERVAL:
52 + retry_timer = 0.0
53 + rate_limited_log("[Network Recovery] 10 seconds elapsed. Attempting background reconnect...")
54 + silent_client_reconnect()
55 +
56 + @rpc("any_peer", "call_local", "reliable")
57 + func receive_secure_clipboard(encrypted_data: PackedByteArray) -> void:
58 + var decrypted_text = decrypt_string(encrypted_data)
59 + clipboard_text = decrypted_text
60 + DisplayServer.clipboard_set(decrypted_text)
61 + update_status("%s | Secure Clip: %s" % [get_network_mode_text(), clipboard_text])
62 +
63 + func start_server(active_port: int) -> void:
64 + is_connected_to_network = false
65 + is_reconnecting = false
66 + is_server_mode = true
67 +
68 + var peer = ENetMultiplayerPeer.new()
69 + if peer.create_server(active_port) == OK:
70 + multiplayer.multiplayer_peer = peer
71 + save_network_config(server_ip, active_port, secret_key)
72 + print("[Network] Server active on port: ", active_port)
73 + else:
74 + rate_limited_log("[Network Error] Failed to start server.")
75 +
76 + func start_client(active_ip: String, active_port: int) -> void:
77 + is_connected_to_network = false
78 + is_server_mode = false
79 +
80 + var peer = ENetMultiplayerPeer.new()
81 + var error = peer.create_client(active_ip, active_port)
82 + if error == OK:
83 + multiplayer.multiplayer_peer = peer
84 + save_network_config(active_ip, active_port, secret_key)
85 + is_reconnecting = true
86 + else:
87 + rate_limited_log("[Network Error] Failed to create client. Error: %d" % error)
88 +
89 + update_status("%s | Secure Clip: %s" % [get_network_mode_text(), clipboard_text])
90 +
91 + # ==========================================
92 + # MULTIPLAYER SIGNAL & RECONNECT CALLBACKS
93 + # ==========================================
94 +
95 + func _on_connected_ok() -> void:
96 + is_connected_to_network = true
97 + is_reconnecting = false
98 + retry_timer = 0.0
99 + print("[Network Status] Connection successful.")
100 + update_status("%s | Secure Clip: %s" % [get_network_mode_text(), clipboard_text])
101 +
102 + func _on_connection_failed() -> void:
103 + is_connected_to_network = false
104 + if not is_reconnecting:
105 + is_reconnecting = true
106 + retry_timer = 0.0
107 + rate_limited_log("[Network Error] Initial handshake failed. Suppressing cascade logs; retrying in 10s.")
108 + update_status("%s | Secure Clip: %s" % [get_network_mode_text(), clipboard_text])
109 +
110 + func _on_disconnected() -> void:
111 + is_connected_to_network = false
112 + is_reconnecting = true
113 + retry_timer = 0.0
114 + print("[Network Status] Disconnected from host. Text changed to YELLOW. Starting 10s retry cadence.")
115 + update_status("%s | Secure Clip: %s" % [get_network_mode_text(), clipboard_text])
116 +
117 + func _on_peer_connected(_id: int) -> void:
118 + if is_server_mode:
119 + is_connected_to_network = true
120 + update_status("%s | Secure Clip: %s" % [get_network_mode_text(), clipboard_text])
121 +
122 + func _on_peer_disconnected(_id: int) -> void:
123 + if is_server_mode and multiplayer.get_peers().size() == 0:
124 + is_connected_to_network = false
125 + update_status("%s | Secure Clip: %s" % [get_network_mode_text(), clipboard_text])
126 +
127 + func silent_client_reconnect() -> void:
128 + multiplayer.multiplayer_peer = null
129 +
130 + var peer = ENetMultiplayerPeer.new()
131 + var error = peer.create_client(server_ip, port_number)
132 + if error == OK:
133 + multiplayer.multiplayer_peer = peer
134 + else:
135 + rate_limited_log("[Network Error] Peer creation failed silently: " + str(error))
136 +
137 + # ==========================================
138 + # AES-256 ENCRYPTION & DECRYPTION LOGIC
139 + # ==========================================
140 +
141 + func get_hashed_key() -> PackedByteArray:
142 + var ctx = HashingContext.new()
143 + ctx.start(HashingContext.HASH_SHA256)
144 + ctx.update(secret_key.to_utf8_buffer())
145 + return ctx.finish()
146 +
147 + func encrypt_string(plain_text: String) -> PackedByteArray:
148 + var aes = AESContext.new()
149 + var key = get_hashed_key()
150 + var data_bytes = plain_text.to_utf8_buffer()
151 + var padding_needed = 16 - (data_bytes.size() % 16)
152 + for i in range(padding_needed):
153 + data_bytes.append(padding_needed)
154 +
155 + aes.start(AESContext.MODE_ECB_ENCRYPT, key)
156 + var encrypted = aes.update(data_bytes)
157 + aes.finish()
158 + return encrypted
159 +
160 + func decrypt_string(encrypted_bytes: PackedByteArray) -> String:
161 + if encrypted_bytes.is_empty(): return ""
162 + var aes = AESContext.new()
163 + var key = get_hashed_key()
164 +
165 + aes.start(AESContext.MODE_ECB_DECRYPT, key)
166 + var decrypted = aes.update(encrypted_bytes)
167 + aes.finish()
168 +
169 + if decrypted.size() > 0:
170 + var padding_count = decrypted[decrypted.size() - 1]
171 + if padding_count > 0 and padding_count <= 16:
172 + decrypted = decrypted.slice(0, decrypted.size() - padding_count)
173 + return decrypted.get_string_from_utf8()
174 +
175 + # ==========================================
176 + # CONFIGURATION & UTILITIES
177 + # ==========================================
178 +
179 + func load_network_config() -> bool:
180 + var config = ConfigFile.new()
181 + var error = config.load(CONFIG_PATH)
182 + if error == OK:
183 + server_ip = config.get_value("Network", "server_ip", "127.0.0.1")
184 + port_number = config.get_value("Network", "port_number", 8443)
185 + secret_key = config.get_value("Network", "secret_key", "ChangeMe123!")
186 + return true
187 + else:
188 + save_network_config("127.0.0.1", 8443, "ChangeMe123!")
189 + return false
190 +
191 + func save_network_config(ip: String, port: int, key: String) -> void:
192 + var config = ConfigFile.new()
193 + config.set_value("Network", "server_ip", ip)
194 + config.set_value("Network", "port_number", port)
195 + config.set_value("Network", "secret_key", key)
196 + if config.save(CONFIG_PATH) == OK:
197 + server_ip = ip
198 + port_number = port
199 + secret_key = key
200 +
201 + func get_network_mode_text() -> String:
202 + var mode_text: String = "Mode: Server" if is_server_mode else "Mode: Client"
203 + if multiplayer.multiplayer_peer == null:
204 + mode_text = "Mode: Offline"
205 + elif is_reconnecting and not is_server_mode:
206 + mode_text = "Mode: Client (Reconnecting...)"
207 + return mode_text
208 +
209 + func update_status(text: String) -> void:
210 + status_changed.emit(text)
211 +
212 + func rate_limited_log(message: String) -> void:
213 + var current_time = Time.get_ticks_msec() / 1000.0
214 + if current_time - last_log_time >= LOG_LIMIT_INTERVAL:
215 + print(message)
216 + last_log_time = current_time
217 +
218 + func generate_random_key(length: int) -> String:
219 + randomize()
220 + var chars: String = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
221 + var result: String = ""
222 + for i in range(length):
223 + result += chars[randi() % chars.length()]
224 + return result
225 +
226 + func copy_key_to_clipboard() -> void:
227 + DisplayServer.clipboard_set(secret_key)
228 + print("Copied to clipboard!")

clipboard_ui.gd(dosya oluşturuldu)

@@ -0,0 +1,159 @@
1 + extends Node
2 + class_name ClipboardUIHelper
3 +
4 + # Builds the Network tab content into a provided container page
5 + static func build_network_ui(page_container: Node, engine: Node) -> void:
6 + ## 1. CLEANUP PREVIOUS RENDERS: Safely look for and wipe old structural layout node
7 + var old_wrapper = page_container.get_node_or_null("NetworkMarginWrapper")
8 + if old_wrapper:
9 + old_wrapper.queue_free()
10 + page_container.remove_child(old_wrapper)
11 +
12 + ## 2. MAIN PAGE LAYOUT: The root wrapper for this specific tab page
13 + var margin_wrapper = MarginContainer.new()
14 + margin_wrapper.name = "NetworkMarginWrapper"
15 + margin_wrapper.add_theme_constant_override("margin_left", 50)
16 + margin_wrapper.add_theme_constant_override("margin_top", 40)
17 + page_container.add_child(margin_wrapper)
18 +
19 + var main_stack = VBoxContainer.new()
20 + main_stack.add_theme_constant_override("separation", 15)
21 + margin_wrapper.add_child(main_stack)
22 +
23 + # ----------------------------------------------------
24 + # ROW 1: Connection Actions (Start Server / Connect Client)
25 + # ----------------------------------------------------
26 + var row_actions = HBoxContainer.new()
27 + row_actions.add_theme_constant_override("separation", 15)
28 + main_stack.add_child(row_actions)
29 +
30 + var btn_server = Button.new()
31 + btn_server.text = "Start Server"
32 + btn_server.pressed.connect(func(): _on_start_server_pressed(engine))
33 + row_actions.add_child(btn_server)
34 +
35 + var btn_client = Button.new()
36 + btn_client.text = "Connect Client"
37 + btn_client.pressed.connect(func(): _on_start_client_pressed(engine))
38 + row_actions.add_child(btn_client)
39 +
40 + # ----------------------------------------------------
41 + # ROW 2: Address Info (IP Input & Port Input)
42 + # ----------------------------------------------------
43 + var row_address = HBoxContainer.new()
44 + row_address.add_theme_constant_override("separation", 15)
45 + main_stack.add_child(row_address)
46 +
47 + var ip_input = LineEdit.new()
48 + ip_input.text = engine.server_ip
49 + ip_input.placeholder_text = "Target Server IP"
50 + ip_input.custom_minimum_size = Vector2(180, 35)
51 + row_address.add_child(ip_input)
52 +
53 + var port_input = LineEdit.new()
54 + port_input.text = str(engine.port_number)
55 + port_input.placeholder_text = "Port"
56 + port_input.custom_minimum_size = Vector2(100, 35)
57 + row_address.add_child(port_input)
58 +
59 + # ----------------------------------------------------
60 + # ROW 3: Security & Utility (Passphrase, Generate, Copy)
61 + # ----------------------------------------------------
62 + var row_security = HBoxContainer.new()
63 + row_security.add_theme_constant_override("separation", 15)
64 + main_stack.add_child(row_security)
65 +
66 + var key_input = LineEdit.new()
67 + key_input.text = engine.secret_key
68 + key_input.secret = true
69 + key_input.placeholder_text = "Shared Secret Key Passphrase"
70 + key_input.custom_minimum_size = Vector2(300, 35)
71 + row_security.add_child(key_input)
72 +
73 + var btn_generate = Button.new()
74 + btn_generate.text = "New Key"
75 + btn_generate.custom_minimum_size = Vector2(0, 35)
76 + btn_generate.pressed.connect(func(): _on_generate_key(engine, key_input))
77 + row_security.add_child(btn_generate)
78 +
79 + var btn_copy = Button.new()
80 + btn_copy.text = "Copy Key"
81 + btn_copy.custom_minimum_size = Vector2(0, 35)
82 + btn_copy.pressed.connect(func(): engine.copy_key_to_clipboard())
83 + row_security.add_child(btn_copy)
84 +
85 + # ----------------------------------------------------
86 + # ROW 4: Manual Sync Input
87 + # ----------------------------------------------------
88 + var row_input = HBoxContainer.new()
89 + row_address.add_theme_constant_override("separation", 15)
90 + main_stack.add_child(row_input)
91 +
92 + var line_edit = LineEdit.new()
93 + line_edit.custom_minimum_size = Vector2(400, 40)
94 + line_edit.placeholder_text = "Type and press Enter to securely sync..."
95 + # Wire the UI input directly to the engine's RPC method using encryption
96 + line_edit.text_submitted.connect(func(new_text): engine.receive_secure_clipboard(engine.encrypt_string(new_text)))
97 + row_input.add_child(line_edit)
98 +
99 + # ----------------------------------------------------
100 + # ROW 5: Status Panel (Replaces draw_rect entirely)
101 + # ----------------------------------------------------
102 + var row_status = HBoxContainer.new()
103 + row_status.add_theme_constant_override("separation", 15)
104 + main_stack.add_child(row_status)
105 +
106 + var status_card = PanelContainer.new()
107 + status_card.custom_minimum_size = Vector2(400, 80)
108 + status_card.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
109 +
110 + var card_padding = MarginContainer.new()
111 + card_padding.add_theme_constant_override("margin_left", 30)
112 + card_padding.add_theme_constant_override("margin_right", 30)
113 + card_padding.add_theme_constant_override("margin_top", 15)
114 + card_padding.add_theme_constant_override("margin_bottom", 15)
115 +
116 + status_card.add_child(card_padding)
117 +
118 + var status_label = RichTextLabel.new()
119 + status_label.text = "Loading..."
120 + status_label.selection_enabled = true
121 + status_label.add_theme_font_size_override("font_size", 18)
122 + status_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
123 + card_padding.add_child(status_label)
124 +
125 + # StyleBoxFlat to mimic your old draw_rect background
126 + var custom_style = StyleBoxFlat.new()
127 + custom_style.bg_color = Color(0.13, 0.13, 0.13, 1.0)
128 + custom_style.set_corner_radius_all(4)
129 + status_card.add_theme_stylebox_override("panel", custom_style)
130 +
131 + main_stack.add_child(status_card)
132 +
133 + # Listen for engine status updates and wire them to the label
134 + engine.status_changed.connect(func(new_status_text): _update_status_label(status_label, new_status_text))
135 +
136 + # -- Static Helper Functions for UI Wiring --
137 +
138 + static func _on_start_server_pressed(engine: Node) -> void:
139 + # Reads current properties. In a real app you might pass inputs as arguments instead.
140 + engine.start_server(int(engine.port_number))
141 +
142 + static func _on_start_client_pressed(engine: Node) -> void:
143 + engine.start_client(str(engine.server_ip), int(engine.port_number))
144 +
145 + static func _on_generate_key(engine: Node, key_input: LineEdit) -> void:
146 + var new_key = engine.generate_random_key(16)
147 + engine.secret_key = new_key
148 + key_input.text = new_key
149 +
150 + static func _update_status_label(label: RichTextLabel, text: String) -> void:
151 + label.text = text
152 + # Calculate dynamic color state based on connection status
153 + var target_color: Color = Color.GREEN
154 + if "Disconnected" in text or "Offline" in text:
155 + target_color = Color.YELLOW
156 + elif "Reconnecting" in text:
157 + target_color = Color.ORANGE
158 +
159 + label.add_theme_color_override("default_color", target_color)

main.gd(dosya oluşturuldu)

@@ -0,0 +1,133 @@
1 + extends Control
2 +
3 + var tab_container: TabContainer
4 + var support_tab_container: TabContainer
5 +
6 + # Reference to the instantiated clipboard/network engine
7 + var clipboard_engine: Node
8 +
9 + func _ready() -> void:
10 + # 1. Initialize the full-screen TabContainer
11 + tab_container = TabContainer.new()
12 + add_child(tab_container)
13 + tab_container.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
14 +
15 + create_standard_tab("Game")
16 + create_standard_tab("Editor")
17 + create_standard_tab("Network")
18 + create_standard_tab("AI")
19 + create_standard_tab("Video")
20 + create_standard_tab("Audio")
21 + create_standard_tab("Support")
22 +
23 + # Connect the tab navigation signal natively
24 + tab_container.tab_changed.connect(_on_tab_changed)
25 +
26 + # Explicitly set the starting tab to ensure a clean state on launch
27 + tab_container.current_tab = 0
28 +
29 + # Instantiate the clipboard/network engine as a child of the root Control.
30 + # This allows it to access the scene tree and multiplayer signals globally.
31 + var engine_script = preload("res://clipboard_engine.gd")
32 + clipboard_engine = engine_script.new()
33 + add_child(clipboard_engine)
34 +
35 + await get_tree().process_frame # Ensure child _ready runs
36 +
37 + # Bootup logic
38 + var has_config = clipboard_engine.load_network_config()
39 + if has_config:
40 + print("[Autoconnect] Secure config file loaded. Instantiating auto-client...")
41 + _switch_to_tab_by_name("Network")
42 + else:
43 + print("[Autoconnect] Initial default config file created.")
44 +
45 + func create_standard_tab(tab_name: String) -> void:
46 + # MarginContainer isolates your tab content from the very edge of the screen
47 + var tab_page := MarginContainer.new()
48 + tab_page.name = tab_name
49 +
50 + # Force the tab page to fill all space provided by the TabContainer
51 + tab_page.size_flags_horizontal = Control.SIZE_EXPAND | Control.SIZE_FILL
52 + tab_page.size_flags_vertical = Control.SIZE_EXPAND | Control.SIZE_FILL
53 +
54 + # Apply standard UI padding (16px - 24px is standard for modern apps)
55 + var padding := 20
56 + tab_page.add_theme_constant_override("margin_top", padding)
57 + tab_page.add_theme_constant_override("margin_left", padding)
58 + tab_page.add_theme_constant_override("margin_right", padding)
59 + tab_page.add_theme_constant_override("margin_bottom", padding)
60 +
61 + tab_container.add_child(tab_page)
62 +
63 + # Add a vertical layout to separate main content from the bottom bar
64 + var layout_vbox := VBoxContainer.new()
65 + layout_vbox.size_flags_horizontal = Control.SIZE_EXPAND | Control.SIZE_FILL
66 + layout_vbox.size_flags_vertical = Control.SIZE_EXPAND | Control.SIZE_FILL
67 + tab_page.add_child(layout_vbox)
68 +
69 + # Placeholder for main content body (Expands to fill top area)
70 + var main_content := Control.new()
71 + main_content.size_flags_horizontal = Control.SIZE_EXPAND | Control.SIZE_FILL
72 + main_content.size_flags_vertical = Control.SIZE_EXPAND | Control.SIZE_FILL
73 + layout_vbox.add_child(main_content)
74 +
75 + # Standard locked bottom node (Only fills width, sits neatly at bottom)
76 + var bottom_bar := PanelContainer.new()
77 + bottom_bar.size_flags_horizontal = Control.SIZE_EXPAND | Control.SIZE_FILL
78 + bottom_bar.size_flags_vertical = Control.SIZE_FILL # Crucial: No expansion flag
79 + layout_vbox.add_child(bottom_bar)
80 +
81 + func _switch_to_tab_by_name(target_name: String) -> void:
82 + # Loop through tabs to match the node name
83 + for i in range(tab_container.get_tab_count()):
84 + var child_node = tab_container.get_child(i)
85 + if child_node.name == target_name:
86 + tab_container.current_tab = i
87 + return
88 +
89 + push_warning("Could not find a tab named: " + target_name)
90 +
91 + func _unhandled_input(event: InputEvent) -> void:
92 + # Handle input events efficiently without polling in _process
93 + if event.is_action_pressed("next_tab"):
94 + _cycle_tabs(1)
95 + elif event.is_action_pressed("previous_tab"):
96 + _cycle_tabs(-1)
97 +
98 + ## Safely increments or decrements the current tab with out-of-bounds protection.
99 + func _cycle_tabs(direction: int) -> void:
100 + var total_tabs: int = tab_container.get_tab_count()
101 +
102 + if total_tabs <= 1:
103 + return
104 +
105 + # posmod() prevents negative numbers and wraps index perfectly
106 + var target_index: int = posmod(tab_container.current_tab + direction, total_tabs)
107 + tab_container.current_tab = target_index
108 +
109 + ## Callback function triggered every time the tab changes.
110 + func _on_tab_changed(tab_index: int) -> void:
111 + var current_page: Node = tab_container.get_child(tab_index)
112 +
113 + match current_page.name:
114 + "Game":
115 + game_interface(current_page)
116 + "Network":
117 + # Pass the container and the engine instance to the UI builder
118 + load("res://clipboard_ui.gd").build_network_ui(current_page, clipboard_engine)
119 + "AI":
120 + ai_interface(current_page)
121 + "Video":
122 + video_settings()
123 + "Audio":
124 + audio_settings()
125 + "Support":
126 + support_interface(current_page)
127 +
128 + # Mock interfaces for other tabs
129 + func game_interface(_page_container: Node) -> void: pass
130 + func ai_interface(_page_container: Node) -> void: pass
131 + func video_settings() -> void: pass
132 + func audio_settings() -> void: pass
133 + func support_interface(_page_container: Node) -> void: pass
Daha yeni Daha eski