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