Последняя активность 1789267469

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

App

ZZ's Avatar ZZ ревизий этого фрагмента 1785660005. К ревизии

1 file changed, 1 insertion, 1 deletion

Godot-System-Tray.gd

@@ -10,7 +10,7 @@ func setup_tray() -> void:
10 10 # 1. Initialize the StatusIndicator
11 11 tray_icon = StatusIndicator.new()
12 12 tray_icon.icon = load("res://ship-icon-256.png") # Path to your 16x16 or 32x32 icon
13 - tray_icon.tooltip = "My Godot App"
13 + tray_icon.tooltip = "EnvoyOS"
14 14 add_child(tray_icon)
15 15
16 16 # 2. Bind the click event to restore the window

ZZ's Avatar ZZ ревизий этого фрагмента 1785659206. К ревизии

Без изменений

ZZ's Avatar ZZ ревизий этого фрагмента 1785658531. К ревизии

1 file changed, 4 insertions, 4 deletions

Godot-Clipboard-Sync.gd

@@ -26,7 +26,7 @@ var port_input: LineEdit
26 26 var key_input: LineEdit
27 27
28 28 func _ready() -> void:
29 - line_edit.position = Vector2(50, 210)
29 + line_edit.position = Vector2(50, 195)
30 30 line_edit.custom_minimum_size = Vector2(400, 40)
31 31 line_edit.placeholder_text = "Type and press Enter to securely sync..."
32 32 line_edit.text_submitted.connect(_on_text_submitted)
@@ -116,12 +116,12 @@ func _on_disconnected() -> void:
116 116 print("[Network Status] Disconnected from host. Text changed to YELLOW. Starting 10s retry cadence.")
117 117 queue_redraw()
118 118
119 - func _on_peer_connected(id: int) -> void:
120 - if is_server_mode:_
119 + func _on_peer_connected(_id: int) -> void:
120 + if is_server_mode:
121 121 is_connected_to_network = true
122 122 queue_redraw()
123 123
124 - func _on_peer_disconnected(id: int) -> void:
124 + func _on_peer_disconnected(_id: int) -> void:
125 125 if is_server_mode and multiplayer.get_peers().size() == 0:
126 126 is_connected_to_network = false
127 127 queue_redraw()

ZZ's Avatar ZZ ревизий этого фрагмента 1785655505. К ревизии

Без изменений

ZZ's Avatar ZZ ревизий этого фрагмента 1785655338. К ревизии

1 file changed, 278 insertions

Godot-Clipboard-Sync.gd(файл создан)

@@ -0,0 +1,278 @@
1 + extends Node2D
2 +
3 + @onready var line_edit: LineEdit = $LineEdit
4 + @onready var synchronizer: MultiplayerSynchronizer = $MultiplayerSynchronizer
5 +
6 + var server_ip: String = "127.0.0.1"
7 + var port_number: int = 8443
8 + var secret_key: String = "ChangeMe123!"
9 +
10 + @export var clipboard_text: String = "Waiting for connection..."
11 +
12 + var is_connected_to_network: bool = false
13 + var is_server_mode: bool = false
14 +
15 + # Reconnection and Rate-Limiting Tracker variables
16 + var is_reconnecting: bool = false
17 + var retry_timer: float = 0.0
18 + const RETRY_INTERVAL: float = 10.0 # Reconnect attempt frequency in seconds
19 + var last_log_time: float = 0.0
20 + const LOG_LIMIT_INTERVAL: float = 10.0 # Suppresses error logs within this timeframe
21 +
22 + const CONFIG_PATH = "user://network_settings.cfg"
23 +
24 + var ip_input: LineEdit
25 + var port_input: LineEdit
26 + var key_input: LineEdit
27 +
28 + func _ready() -> void:
29 + line_edit.position = Vector2(50, 210)
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 +
50 + func _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")
67 + func 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 +
73 + func _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 +
78 + func _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 +
96 + func _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 +
103 + func _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 +
112 + func _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 +
119 + func _on_peer_connected(id: int) -> void:
120 + if is_server_mode:_
121 + is_connected_to_network = true
122 + queue_redraw()
123 +
124 + func _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 +
129 + func 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
141 + func 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 +
151 + func 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 +
157 + func 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 +
170 + func 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 +
189 + func 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 +
205 + func 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 +
219 + func 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 +
254 + func 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 +
266 + func 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()

ZZ's Avatar ZZ ревизий этого фрагмента 1785655307. К ревизии

1 file changed, 1 insertion, 1 deletion

Godot-System-Tray.gd

@@ -9,7 +9,7 @@ func _ready() -> void:
9 9 func setup_tray() -> void:
10 10 # 1. Initialize the StatusIndicator
11 11 tray_icon = StatusIndicator.new()
12 - tray_icon.icon = load("res://icon.svg") # Path to your 16x16 or 32x32 icon
12 + tray_icon.icon = load("res://ship-icon-256.png") # Path to your 16x16 or 32x32 icon
13 13 tray_icon.tooltip = "My Godot App"
14 14 add_child(tray_icon)
15 15

ZZ's Avatar ZZ ревизий этого фрагмента 1785643796. К ревизии

1 file changed, 0 insertions, 0 deletions

Godot-System-Tray.gdscript переименован в Godot-System-Tray.gd

Файл переименован без изменений

ZZ's Avatar ZZ ревизий этого фрагмента 1785643769. К ревизии

1 file changed, 55 insertions

Godot-System-Tray.gdscript(файл создан)

@@ -0,0 +1,55 @@
1 + extends Node
2 +
3 + var tray_icon: StatusIndicator
4 + var tray_menu: PopupMenu
5 +
6 + func _ready() -> void:
7 + setup_tray()
8 +
9 + func setup_tray() -> void:
10 + # 1. Initialize the StatusIndicator
11 + tray_icon = StatusIndicator.new()
12 + tray_icon.icon = load("res://icon.svg") # Path to your 16x16 or 32x32 icon
13 + tray_icon.tooltip = "My Godot App"
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
30 + func _notification(what: int) -> void:
31 + if what == NOTIFICATION_WM_CLOSE_REQUEST:
32 + minimize_to_tray()
33 +
34 + # Hides the application entirely from desktop view
35 + func 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
42 + func 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
47 + func _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
52 + func _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
Новее Позже