Остання активність 1785695986

Systray app behavior: Go to Project > Project Settings > Engine > Config > Uncheck Auto Accept Quit

Версія 9f0a867da7a0e503fd0f5f26257eee62c580e901

Godot-Clipboard-Sync.gd Неформатований
1extends Node2D
2
3@onready var line_edit: LineEdit = $LineEdit
4@onready var synchronizer: MultiplayerSynchronizer = $MultiplayerSynchronizer
5
6var server_ip: String = "127.0.0.1"
7var port_number: int = 8443
8var secret_key: String = "ChangeMe123!"
9
10@export var clipboard_text: String = "Waiting for connection..."
11
12var is_connected_to_network: bool = false
13var is_server_mode: bool = false
14
15# Reconnection and Rate-Limiting Tracker variables
16var is_reconnecting: bool = false
17var retry_timer: float = 0.0
18const RETRY_INTERVAL: float = 10.0 # Reconnect attempt frequency in seconds
19var last_log_time: float = 0.0
20const LOG_LIMIT_INTERVAL: float = 10.0 # Suppresses error logs within this timeframe
21
22const CONFIG_PATH = "user://network_settings.cfg"
23
24var ip_input: LineEdit
25var port_input: LineEdit
26var key_input: LineEdit
27
28func _ready() -> void:
29 line_edit.position = Vector2(50, 195)
30 line_edit.custom_minimum_size = Vector2(400, 40)
31 line_edit.placeholder_text = "Type and press Enter to securely sync..."
32 line_edit.text_submitted.connect(_on_text_submitted)
33
34 # Connect Godot's network lifecycle signals
35 multiplayer.connected_to_server.connect(_on_connected_ok)
36 multiplayer.connection_failed.connect(_on_connection_failed)
37 multiplayer.server_disconnected.connect(_on_disconnected)
38 multiplayer.peer_connected.connect(_on_peer_connected)
39 multiplayer.peer_disconnected.connect(_on_peer_disconnected)
40
41 create_network_interface()
42
43 var has_config = load_network_config()
44 if has_config:
45 print("[Autoconnect] Secure config file loaded. Instantiating auto-client...")
46 start_client()
47 else:
48 print("[Autoconnect] Initial default config file created.")
49
50func _process(delta: float) -> void:
51 # 1. Manage Clipboard Synchronization
52 var current_sys_clip = DisplayServer.clipboard_get()
53 if current_sys_clip != clipboard_text:
54 if multiplayer.is_server() or line_edit.has_focus() or current_sys_clip != "":
55 var encrypted_payload = encrypt_string(current_sys_clip)
56 rpc("receive_secure_clipboard", encrypted_payload)
57
58 # 2. Manage Automatic Client Reconnection Loop
59 if is_reconnecting and not is_server_mode:
60 retry_timer += delta
61 if retry_timer >= RETRY_INTERVAL:
62 retry_timer = 0.0
63 rate_limited_log("[Network Recovery] 10 seconds elapsed. Attempting background reconnect...")
64 silent_client_reconnect()
65
66@rpc("any_peer", "call_local", "reliable")
67func receive_secure_clipboard(encrypted_data: PackedByteArray) -> void:
68 var decrypted_text = decrypt_string(encrypted_data)
69 clipboard_text = decrypted_text
70 DisplayServer.clipboard_set(decrypted_text)
71 queue_redraw()
72
73func _on_text_submitted(new_text: String) -> void:
74 var encrypted_payload = encrypt_string(new_text)
75 rpc("receive_secure_clipboard", encrypted_payload)
76 line_edit.text = ""
77
78func _draw() -> void:
79 draw_rect(Rect2(50, 270, 500, 80), Color(0.15, 0.15, 0.15, 1.0))
80 var system_font = ThemeDB.fallback_font
81 var draw_position = Vector2(60, 320)
82
83 var network_status = "Mode: Server" if is_server_mode else "Mode: Client"
84 if multiplayer.multiplayer_peer == null:
85 network_status = "Mode: Offline"
86 elif is_reconnecting and not is_server_mode:
87 network_status = "Mode: Client (Reconnecting...)"
88
89 var current_text_color = Color.GREEN if is_connected_to_network else Color.YELLOW
90 draw_string(system_font, draw_position, network_status + " | Secure Clip: " + clipboard_text, HORIZONTAL_ALIGNMENT_LEFT, -1, 18, current_text_color)
91
92# ==========================================
93# MULTIPLAYER SIGNAL & RECONNECT CALLBACKS
94# ==========================================
95
96func _on_connected_ok() -> void:
97 is_connected_to_network = true
98 is_reconnecting = false
99 retry_timer = 0.0
100 print("[Network Status] Connection successful. Text changed to GREEN.")
101 queue_redraw()
102
103func _on_connection_failed() -> void:
104 is_connected_to_network = false
105 # Trigger the reconnection routine cleanly if we aren't already hunting a link
106 if not is_reconnecting:
107 is_reconnecting = true
108 retry_timer = 0.0
109 rate_limited_log("[Network Error] Initial handshake failed. Suppressing cascade logs; retrying in 10s.")
110 queue_redraw()
111
112func _on_disconnected() -> void:
113 is_connected_to_network = false
114 is_reconnecting = true
115 retry_timer = 0.0
116 print("[Network Status] Disconnected from host. Text changed to YELLOW. Starting 10s retry cadence.")
117 queue_redraw()
118
119func _on_peer_connected(_id: int) -> void:
120 if is_server_mode:
121 is_connected_to_network = true
122 queue_redraw()
123
124func _on_peer_disconnected(_id: int) -> void:
125 if is_server_mode and multiplayer.get_peers().size() == 0:
126 is_connected_to_network = false
127 queue_redraw()
128
129func silent_client_reconnect() -> void:
130 # Explicitly clean up old hanging connection references before attempting a new socket allocation
131 multiplayer.multiplayer_peer = null
132
133 var peer = ENetMultiplayerPeer.new()
134 var error = peer.create_client(server_ip, port_number)
135 if error == OK:
136 multiplayer.multiplayer_peer = peer
137 else:
138 rate_limited_log("[Network Error] Peer creation failed silently: " + str(error))
139
140# Custom logger to ensure your output terminal stays pristine instead of printing millions of loops
141func rate_limited_log(message: String) -> void:
142 var current_time = Time.get_ticks_msec() / 1000.0
143 if current_time - last_log_time >= LOG_LIMIT_INTERVAL:
144 print(message)
145 last_log_time = current_time
146
147# ==========================================
148# AES-256 ENCRYPTION & DECRYPTION LOGIC
149# ==========================================
150
151func get_hashed_key() -> PackedByteArray:
152 var ctx = HashingContext.new()
153 ctx.start(HashingContext.HASH_SHA256)
154 ctx.update(secret_key.to_utf8_buffer())
155 return ctx.finish()
156
157func encrypt_string(plain_text: String) -> PackedByteArray:
158 var aes = AESContext.new()
159 var key = get_hashed_key()
160 var data_bytes = plain_text.to_utf8_buffer()
161 var padding_needed = 16 - (data_bytes.size() % 16)
162 for i in range(padding_needed):
163 data_bytes.append(padding_needed)
164
165 aes.start(AESContext.MODE_ECB_ENCRYPT, key)
166 var encrypted = aes.update(data_bytes)
167 aes.finish()
168 return encrypted
169
170func decrypt_string(encrypted_bytes: PackedByteArray) -> String:
171 if encrypted_bytes.is_empty(): return ""
172 var aes = AESContext.new()
173 var key = get_hashed_key()
174
175 aes.start(AESContext.MODE_ECB_DECRYPT, key)
176 var decrypted = aes.update(encrypted_bytes)
177 aes.finish()
178
179 if decrypted.size() > 0:
180 var padding_count = decrypted[decrypted.size() - 1]
181 if padding_count > 0 and padding_count <= 16:
182 decrypted = decrypted.slice(0, decrypted.size() - padding_count)
183 return decrypted.get_string_from_utf8()
184
185# ==========================================
186# CONFIGURATION FILE SAVE & LOAD LOGIC
187# ==========================================
188
189func load_network_config() -> bool:
190 var config = ConfigFile.new()
191 var error = config.load(CONFIG_PATH)
192 if error == OK:
193 server_ip = config.get_value("Network", "server_ip", "127.0.0.1")
194 port_number = config.get_value("Network", "port_number", 8443)
195 secret_key = config.get_value("Network", "secret_key", "ChangeMe123!")
196 if ip_input and port_input and key_input:
197 ip_input.text = server_ip
198 port_input.text = str(port_number)
199 key_input.text = secret_key
200 return true
201 else:
202 save_network_config("127.0.0.1", 8443, "ChangeMe123!")
203 return false
204
205func save_network_config(ip: String, port: int, key: String) -> void:
206 var config = ConfigFile.new()
207 config.set_value("Network", "server_ip", ip)
208 config.set_value("Network", "port_number", port)
209 config.set_value("Network", "secret_key", key)
210 if config.save(CONFIG_PATH) == OK:
211 server_ip = ip
212 port_number = port
213 secret_key = key
214
215# ==========================================
216# INTERFACE IMPLEMENTATION
217# ==========================================
218
219func create_network_interface() -> void:
220 var btn_server = Button.new()
221 btn_server.text = "Start Server"
222 btn_server.position = Vector2(50, 40)
223 btn_server.pressed.connect(start_server)
224 add_child(btn_server)
225
226 var btn_client = Button.new()
227 btn_client.text = "Connect Client"
228 btn_client.position = Vector2(160, 40)
229 btn_client.pressed.connect(start_client)
230 add_child(btn_client)
231
232 ip_input = LineEdit.new()
233 ip_input.text = server_ip
234 ip_input.placeholder_text = "Target Server IP"
235 ip_input.position = Vector2(50, 100)
236 ip_input.custom_minimum_size = Vector2(180, 35)
237 add_child(ip_input)
238
239 port_input = LineEdit.new()
240 port_input.text = str(port_number)
241 port_input.placeholder_text = "Port"
242 port_input.position = Vector2(250, 100)
243 port_input.custom_minimum_size = Vector2(100, 35)
244 add_child(port_input)
245
246 key_input = LineEdit.new()
247 key_input.text = secret_key
248 key_input.secret = true
249 key_input.placeholder_text = "Shared Secret Key Passphrase"
250 key_input.position = Vector2(50, 150)
251 key_input.custom_minimum_size = Vector2(300, 35)
252 add_child(key_input)
253
254func start_server() -> void:
255 is_connected_to_network = false
256 is_reconnecting = false
257 is_server_mode = true
258 var active_port = int(port_input.text)
259 save_network_config(ip_input.text, active_port, key_input.text)
260 var peer = ENetMultiplayerPeer.new()
261 if peer.create_server(active_port) != OK: return
262 multiplayer.multiplayer_peer = peer
263 print("[Network] Server active on port: ", active_port)
264 queue_redraw()
265
266func start_client() -> void:
267 is_connected_to_network = false
268 is_server_mode = false
269 var active_ip = ip_input.text
270 var active_port = int(port_input.text)
271 save_network_config(active_ip, active_port, key_input.text)
272
273 var peer = ENetMultiplayerPeer.new()
274 var error = peer.create_client(active_ip, active_port)
275 if error == OK:
276 multiplayer.multiplayer_peer = peer
277 is_reconnecting = true # Instantly engage our automated monitoring pipeline
278 queue_redraw()
279
Godot-System-Tray.gd Неформатований
1extends Node
2
3var tray_icon: StatusIndicator
4var tray_menu: PopupMenu
5
6func _ready() -> void:
7 setup_tray()
8
9func setup_tray() -> void:
10 # 1. Initialize the StatusIndicator
11 tray_icon = StatusIndicator.new()
12 tray_icon.icon = load("res://ship-icon-256.png") # Path to your 16x16 or 32x32 icon
13 tray_icon.tooltip = "EnvoyOS"
14 add_child(tray_icon)
15
16 # 2. Bind the click event to restore the window
17 tray_icon.pressed.connect(_on_tray_icon_pressed)
18
19 # 3. Optional: Create a right-click context menu
20 tray_menu = PopupMenu.new()
21 tray_menu.add_item("Show Window", 0)
22 tray_menu.add_item("Quit", 1)
23 tray_menu.id_pressed.connect(_on_menu_item_pressed)
24 add_child(tray_menu)
25
26 # Assign menu to the tray icon
27 tray_icon.menu = tray_menu.get_path()
28
29# Handle window close interception
30func _notification(what: int) -> void:
31 if what == NOTIFICATION_WM_CLOSE_REQUEST:
32 minimize_to_tray()
33
34# Hides the application entirely from desktop view
35func minimize_to_tray() -> void:
36 # Hide window from taskbar and ignore standard user inputs
37 DisplayServer.window_set_flag(DisplayServer.WINDOW_FLAG_NO_FOCUS, true)
38 # Hide the actual window frame
39 DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_MINIMIZED)
40
41# Restores the application window back to normal
42func restore_from_tray() -> void:
43 DisplayServer.window_set_flag(DisplayServer.WINDOW_FLAG_NO_FOCUS, false)
44 DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED)
45
46# Handle clicking directly on the tray icon
47func _on_tray_icon_pressed(mouse_button: int, _device_id: int) -> void:
48 if mouse_button == MOUSE_BUTTON_LEFT:
49 restore_from_tray()
50
51# Handle context menu actions
52func _on_menu_item_pressed(id: int) -> void:
53 match id:
54 0: restore_from_tray()
55 1: get_tree().quit() # Ensure clean termination when explicitly chosen
56