EnvoyOS — Multi-PC, Multi-Monitor Productivity Suite
A productivity-focused desktop application built with Godot 4.6. Designed for engineers and knowledge workers who work across multiple PCs and monitors. The core value proposition: every keystroke, file, and timer is one hop away regardless of which machine you're sitting at.
Project Highlights
- All-in-One Toolkit: AI tools, clipboard sync, file transfer, timers, inventory tracking, video/audio settings, and built-in support documentation in a single interface.
- Modular Architecture: Clean separation between logic (
*_engine.gd) and UI (*_ui.gd) ensures scalability and easy maintenance. - Cross-PC Sync: ENet-based multiplayer with AES-256 encrypted clipboard, planned file transfer, and shared state.
- Multi-Monitor Aware: DPI scaling, per-monitor layout memory, window tiling, and floating overlays.
- AI Integration: Local Ollama-powered code assistant, suggestions, and (planned) transcription.
Why Support?
Developing and maintaining open-source tools takes time, effort, and resources. Your support directly funds:
- New feature development & bug fixes
- Performance optimizations & engine updates
- Documentation & community resources
Every contribution, no matter the size, helps keep this project growing and accessible to everyone. Thank you for your support! ☕✨
Current State
Completed / Working
| Module | Status |
|---|---|
| Clipboard sync (ENet + AES-256) | ✅ Working |
| AI Chat (Ollama streaming) | ✅ Working |
| Markdown Editor (autosave) | ✅ Working |
| VoIP (UDP, VAD, jitter buffer) | 🚧 In Progress |
| Audio settings (volume, mute, mic level) | 🚧 In Progress |
| Timer panel (12 slots, fuel-bar UI) | ✅ Working |
| Inventory panel (12 slots, fuel-bar UI) | 🚧 In Progress |
| Tab management (custom tab bar, session persistence) | ✅ Working |
| OS Grid layout (configurable rows × cols) | 🚧 In Progress |
| Theme builder (bg/font/accent/shade/transparency) | ✅ Working |
| Video settings (FPS, DPI, window mode, position, size) | ✅ Working |
| Web tab browser (paginated Chrome export) | 🚧 In Progress |
| Ship game (2D physics, object pooling) | ✅ Working |
| TOP / Power state tracker | 🚧 In Progress |
| User AFK detection (cross-platform) | ✅ Working |
| Window drag/resize (borderless) | ✅ Working |
| System tray minimization | ✅ Working |
Missing / Stub (Blocking)
| File | Status | Action Required |
|---|---|---|
system_engine.gd |
Empty | Implement CPU/RAM/disk/GPU stats |
system_ui.gd |
Empty | Build system dashboard (CPU graph, RAM bar, disk, AFK) |
ai_code_engine.gd |
Empty | Build code-gen/review/explain engine |
ai_code_ui.gd |
Empty | Side-by-side code editor + AI panel |
music_engine.gd |
extends Node only |
Implement playback queue, seeking, shuffle |
Known Bugs / Technical Debt
| Issue | Location | Fix |
|---|---|---|
grid_engine.gd references clipboard_engine.is_connected_to_network (doesn't exist) |
grid_engine.gd line ~230 |
Change to clipboard_engine.can_broadcast / clipboard_engine.is_connecting |
tab_engine.gd and top_engine.gd are 95% duplicates |
Both files | Merge into one class or have TopEngine extend TabEngine |
| No input validation on VoIP port field | voip_ui.gd |
Add is_valid_int() check before int() cast |
Timer/Inventory hh:mm:ss parse has no overflow guard |
time_ui.gd, inventory_ui.gd |
Validate h≤23, m≤59, s≤59 before arithmetic |
| Music/Media tab is a dead tab | main_engine.gd ALL_TABS |
Either implement or remove from list |
| No CI / automated testing | Repo root | Add GitHub Actions with godot --headless --check-only |
Feature Roadmap
Phase 1 — Fix & Stabilise (Weeks 1–2)
| # | Task | Details |
|---|---|---|
| 1.1 | Implement system_engine.gd |
CPU %, RAM used/total, disk usage, GPU load (platform-specific), network throughput. Emit signals for UI. |
| 1.2 | Implement system_ui.gd |
Dashboard with live-updating gauges, AFK timer (reuse user_active_engine.gd), process list (optional). |
| 1.3 | Fix grid_engine.gd broken refs |
Replace is_connected_to_network → can_broadcast, is_reconnecting → is_connecting. |
| 1.4 | Merge tab_engine / top_engine |
One base class, reduce duplication. |
| 1.5 | Add input validation | VoIP port, Timer/Inventory time parsing, all user-facing number fields. |
| 1.6 | Implement or remove Music tab | Either build music_engine.gd + wire music_ui.gd, or remove "Media" from ALL_TABS. |
| 1.7 | Add unit tests (GUT) | ThemeSerializer round-trip, TabEngine CRUD, TimeEngine tick, InventoryEngine expiry, ClipboardEngine encrypt/decrypt. |
| 1.8 | CI pipeline | GitHub Actions: godot --headless --check-only + GUT test run on push. |
Phase 2 — Multi-PC Power Features (Weeks 3–6)
| # | Task | Details |
|---|---|---|
| 2.1 | Clipboard History Ring Buffer | Store last 100 items (text + images) with timestamp, source PC, app name. Global hotkey Ctrl+Shift+V opens searchable picker across all peers. |
| 2.2 | Snippet Library | Save frequently-used text blocks (code snippets, email templates, API keys). Sync to all PCs. One-click paste. |
| 2.3 | Cross-PC Text Search | Type a keyword → search all peers' clipboard history simultaneously via RPC. |
| 2.4 | P2P File Transfer | Drag file onto Network tab → AES-256 encrypt → chunked ENet stream → progress bar → destination PC. Reuse existing crypto. |
| 2.5 | Shared Workspace Folder | Synced directory (chunked transfer). Edit on PC-A, open on PC-B. |
| 2.6 | Shared Kanban Board | To-Do / In-Progress / Done. RPC-synced in real-time. Cards: title, tags, due date, assigned PC. Reuse clipboard_engine RPC pattern. |
| 2.7 | Notification Mirroring | System/app notifications broadcast to all peers. "PC-A idle > 10 min" → toast on PC-B. Timer expiry → alert all. |
| 2.8 | Command Palette (Ctrl+K) |
Fuzzy-search all actions: "open tab", "change theme", "start timer 3", "connect to PC-B", "mute audio". One keystroke to do anything. |
| 2.9 | Global Hotkeys (OS-level) | Register via native helper: clipboard history, screenshot, quick paste. Works even when app is unfocused. |
| 2.10 | Offline Queue | When disconnected, queue clipboard/file/task changes. Auto-sync on reconnect (extend existing reconnection logic). |
Phase 3 — Multi-Monitor & AI (Weeks 7–10)
| # | Task | Details |
|---|---|---|
| 3.1 | Monitor Layout Memory | Detect monitors, save per-monitor window positions/sizes per panel. Restore on reconnect. user://monitor_layout.json. |
| 3.2 | Floating Overlays | Always-on-top widgets: timer countdown, clipboard dock (last 5 items), AFK indicator glow. Assignable to specific monitor. |
| 3.3 | Window Tiling / Snapping | Ctrl+1..4 → quadrant. Ctrl+Shift+Left/Right → half. Save/load named layouts. Ctrl+L to cycle. |
| 3.4 | Per-Monitor Contrast | Each monitor can have different shade/brightness. Auto-adjust based on ambient light or time-of-day. |
| 3.5 | AI Code Engine | ai_code_engine.gd: Explain selected code, Refactor + diff view, Generate from prompt, Review PR diff. Reuse Ollama streaming client. |
| 3.6 | AI Code UI | ai_code_ui.gd: Side-by-side editor + AI response panel. Syntax highlighting, diff view, copy-to-clipboard. |
| 3.7 | AI Meeting Notes | Stream mic audio → local Whisper (Ollama-compatible) → bullet-point summary → sync to all peers. |
| 3.8 | Cross-PC Input Mirroring (KVM) | Type on PC-A → keystrokes stream to PC-B. Mouse delta streaming (relative, not absolute). Per-peer toggle. |
| 3.9 | AI Suggestion Engine | Wire existing "Suggestion" prompt field to a "Smart Suggestions" button on clipboard tab. 3 context-aware actions. |
Phase 4 — Polish & Release (Weeks 11–12)
| # | Task | Details |
|---|---|---|
| 4.1 | Auto-start with OS | Registry key (Win), .desktop file (Linux), LaunchAgent (macOS). |
| 4.2 | System Tray Menu | "Show", "Mute", "Disconnect", "Open Timer", "Open Kanban", "Exit". |
| 4.3 | Backup / Restore | One-click export all user:// settings to .zip. Import on new PC. |
| 4.4 | Accessibility Pass | High-contrast mode, UI font scaling, screen-reader labels on all controls. |
| 4.5 | Session Replay | Record 10s UI interaction replay as JSON for bug reports. |
| 4.6 | User Documentation | Expand SUMMARY.md → full user manual. Keyboard shortcut reference. Setup guide per OS. |
| 4.7 | Export / Packaging | godot --export-release for Windows / Linux / macOS. In-app updater (optional). |
Phase 5 — Differentiators (Ongoing)
| # | Feature | Description |
|---|---|---|
| 5.1 | Screen recording per monitor | GPU capture, "record this monitor" toggle, export to file. |
| 5.2 | Screenshot + AI annotate | Capture region → send to Ollama → written description. |
| 5.3 | Pomodoro focus mode | 25/5 preset. Auto-pauses AI streaming and network sync during focus. |
| 5.4 | Dark-light auto-switch | Theme tied to system clock (dark after 18:00). |
| 5.5 | Peer presence bar | Avatar strip showing online PCs (reuse multiplayer.get_peers()). |
| 5.6 | Voice commands | "Hey, paste the last email" → VAD → match snippet → paste. |
| 5.7 | Shared timer countdown | Timer on PC-A shows live on PC-B. Sync end_timestamp over network. |
| 5.8 | Calendar view | Timers/inventory deadlines on a mini monthly calendar. |
| 5.9 | CSV export | Timer/inventory state as CSV for spreadsheet tracking. |
| 5.10 | Low-stock alerts | Inventory item below threshold → red flag on all PCs. |
Architecture Notes
┌─────────────────────────────────────────────────────────────────────┐
│ MainUI / GridUI (Root) │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ TabBar / Grid Toolbar │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ TabContainer / Content Area │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ AI Chat │ │ Editor │ │ Network │ │ Timer │ ... │ │
│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ Engines (Nodes) │ │
│ │ clipboard_engine │ ai_engine │ video_engine │ audio_engine │ │
│ │ mic_engine │ web_engine │ time_engine │ inventory_engine │ │
│ │ ui_engine │ top_engine │ tab_engine │ voip_engine │ │
│ └───────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
Network Layer: ENet (MultiplayerPeer) + AES-256 (AESContext)
Persistence: ConfigFile (*.cfg) + JSON (*.json) + CSV (timers/inventory)
Theme: ThemeSerializer (compressed JSON, hex colors, FastLZ)
Supported Platforms
| OS | Clipboard | Audio | Idle Detection | Network |
|---|---|---|---|---|
| Windows | ✅ | WASAPI | GetLastInputInfo (P/Invoke) | ENet |
| macOS | ✅ | CoreAudio | IOHIDSystem (ioreg) | ENet |
| Linux | ✅ | PipeWire/PulseAudio | D-Bus (Mutter/KWin) / xprintidle | ENet |
Keyboard Shortcuts (Planned)
| Shortcut | Action |
|---|---|
Ctrl+K |
Command Palette |
Ctrl+Shift+V |
Clipboard History (cross-PC) |
Ctrl+1..4 |
Snap to quadrant |
Ctrl+Shift+Left/Right |
Snap to half |
Ctrl+L |
Cycle saved layouts |
Ctrl+W / close_tab |
Close current tab |
Ctrl+T / new_tab |
New tab |
Ctrl+Tab / Ctrl+Shift+Tab |
Next / Previous tab |
Why This Saves Time
| Pain Point | Solution | Time Saved |
|---|---|---|
| Copy on PC-A, walk to PC-B, paste | Cross-PC clipboard sync | 30s–2min per transfer |
| Re-type same email/snippet on each machine | Snippet library + one-click paste | 1–5min per repetition |
| "Where did I save that file?" | Shared workspace + cross-PC search | 5–15min per search |
| Set up different configs per monitor | Monitor layout memory + auto-restore | 5–10min per session |
| Explain code to colleague on another screen | AI explain + share to all PCs | 5–10min per explanation |
| Track physical inventory across desk drawers | Shared inventory with expiry alerts | Ongoing |
| "Did I finish that task?" | Shared Kanban visible on all PCs | 2–5min per check |
| Context-switch between 4 monitors on 2 PCs | Window tiling + input mirroring | 1–3min per switch |
Contribution Guidelines
- Read
AIFIX.mdfor Godot 4.6 API corrections before editing. - Follow the
*_engine.gd/*_ui.gdnaming convention. - All new UI must handle tab-switch cleanup (check for existing wrapper before adding).
- Network changes must go through
clipboard_engine.gdRPC pattern (or the newMultiplayerChannelhelper). - Theme changes must flow through
ui_engine.gd→MainUI.apply_full_theme(). - Run tests:
godot --headless -s res://addons/gut/gut_cmdln.gd -gdir=res://tests
License & Attribution
See repository for license file. Built with ❤️ and too much coffee. ☕
Godot 4.7 — Project Conventions & API Reference
A reference document for the EnvoyOS project targeting Godot 4.7. Derived from the official Godot 4.7 documentation structure and the project's AIFIX.md corrections.
Table of Contents
- GDScript Syntax & Type Hints
- Node Lifecycle
- Signals
- Input Handling
- Controls & UI
- Window Management
- Audio
- Networking (Multiplayer)
- File I/O
- Resources & Loading
- Performance
- Style Conventions
- Common Pitfalls & Corrections
1. GDScript Syntax & Type Hints
Typed Arrays
# WRONG (Godot 3 style, inferred empty array)
var rocks := []
# CORRECT (Godot 4.7 explicit type)
var rocks: Array[Label] = []
var items: Array[Dictionary] = []
var names: Array[String] = []
var scales: Array[float] = []
Variable Declarations
# Prefer explicit types over := inference when the type is unambiguous
var file: FileAccess = FileAccess.open(path, FileAccess.READ)
var parsed: Variant = JSON.parse_string(json_str)
# Use := only when the right-hand side type is clear
var btn := Button.new()
var style := StyleBoxFlat.new()
Color Construction
# WRONG
col.color = Color.rand()
# CORRECT
col.color = Color(randf(), randf(), randf())
Global Functions (Not Methods)
# WRONG (clampf is a global function, not a method on float)
_transparency_multiplier = value.clampf(0.0, 1.0)
# CORRECT
_transparency_multiplier = clampf(value, 0.0, 1.0)
# OR
_transparency_multiplier = clamp(value, 0.0, 1.0)
Vector Operations
# WRONG (ceiled() does not exist on Vector2)
var target := vec.ceiled()
# CORRECT
var target := Vector2i(vec.ceil())
Async/Await
# WRONG (yield was removed in Godot 4)
yield(get_tree().process_frame, "process_frame")
# CORRECT
await get_tree().process_frame
Process Delta Time
# WRONG (throws error)
var speed = Node.get_process_delta_time()
# CORRECT
var speed = get_process_delta_time()
Constants & Enums
const MAX_ITEMS := 12
const CONFIG_PATH := "user://inventory_config.cfg"
enum PowerState {
OFF = 0,
LOW = 1,
NETWORK = 2,
HIGH = 3,
}
2. Node Lifecycle
Key Notifications (Godot 4.7)
func _notification(what: int) -> void:
match what:
NOTIFICATION_WM_CLOSE_REQUEST:
# Window close (replaces NOTIFICATION_WM_QUIT_REQUEST)
pass
NOTIFICATION_APPLICATION_PAUSED, NOTIFICATION_APPLICATION_FOCUS_OUT:
# App backgrounded / lost focus
pass
NOTIFICATION_APPLICATION_RESUMED, NOTIFICATION_APPLICATION_FOCUS_IN:
# App foregrounded / regained focus
pass
Process Mode (Replaces Old Pause Mode)
# WRONG
timer.pause_mode = Timer.PAUSE_MODE_PROCESS
# CORRECT
timer.process_mode = Node.PROCESS_MODE_ALWAYS
Dialogs and Timers
# When a ConfirmationDialog is shown via popup_centered(),
# the SceneTree pauses. Timers inside will stop unless:
dialog.process_mode = Node.PROCESS_MODE_ALWAYS
Node Creation and Freeing
# To clear a container's children (VBoxContainer has no .clear() in Godot 4)
# WRONG
kitchen_sink_box.clear()
# CORRECT
for child in container.get_children():
child.queue_free()
Safe Instance Checks
# After any await or deferred call, verify the object still exists
if is_instance_valid(node) and node.is_inside_tree():
node.do_something()
3. Signals
Declaration
signal item_updated(index: int)
signal stats_updated(latency_ms: float, jitter: float, packet_loss: float)
Connecting (with type-safe lambdas)
btn.pressed.connect(_on_button_pressed.bind(index))
slider.value_changed.connect(func(val: float): _on_slider(val))
Disconnecting
if signal_name in node.get_signal_connection_list(sig):
node.disconnect(sig, callable)
RPC (Multiplayer)
@rpc("any_peer", "unreliable_ordered")
func receive_secure_clipboard(encrypted_data: PackedByteArray) -> void:
pass
# Call from any peer:
rpc("receive_secure_clipboard", data)
4. Input Handling
Key Codes (Godot 4.7)
if event is InputEventKey and event.keycode == KEY_TAB and Input.is_key_pressed(KEY_CTRL):
pass
Touchscreen
# WRONG
Input.is_touchscreen_available()
# CORRECT
DisplayServer.is_touchscreen_available()
WebRTC Feature Check
# WRONG
Engine.has_feature("web_view")
# CORRECT
OS.has_feature("web") or ClassDB.class_exists("WebRTCPeerConnection")
Mouse Position (Global vs Local)
# For window-level calculations (resize, drag), use GLOBAL screen coords:
var global_mouse: Vector2 = DisplayServer.mouse_get_position()
# For UI-local calculations:
var local_mouse: Vector2 = get_viewport().get_mouse_position()
5. Controls & UI
StyleBoxFlat (Per-Corner, Per-Side)
# WRONG (Godot 3 properties do not exist in 4.7)
style.border_width_all = 2
style.corner_radius_all = 16
# CORRECT (individual properties)
style.border_width_left = 2
style.border_width_right = 2
style.border_width_top = 2
style.border_width_bottom = 2
style.corner_radius_top_left = 16
style.corner_radius_top_right = 16
style.corner_radius_bottom_left = 16
style.corner_radius_bottom_right = 16
StyleBox Copying
# WRONG
var hover_style = StyleBoxFlat.new(style)
# CORRECT
var hover_style = style.duplicate()
TextEdit Wrapping
# WRONG
system_input.fit_content_width = true
# CORRECT
system_input.wrap_mode = TextEdit.LINE_WRAPPING_BOUNDARY
Tree Items
# WRONG (type inference fails)
var child := control.create_item(root)
# CORRECT
var child: TreeItem = control.create_item(root)
Abstract Classes
# WRONG (Slider is abstract, cannot be constructed)
Slider.new()
# CORRECT
VSlider.new()
HSlider.new()
TabBar Close Buttons
# WRONG (property does not exist)
tab_bar.tabs_closeable = true
# CORRECT
tab_bar.tab_close_display_policy = TabBar.CLOSE_BUTTON_SHOW_ALWAYS
tab_bar.tab_close_pressed.connect(_on_tab_close_pressed)
Hiding TabContainer's Built-in Tab Bar
tab_container.tabs_visible = false
ConfirmationDialog
# Godot 4.7: use get_cancel_button() (not get_cancel())
dialog.get_cancel_button().text = "Cancel"
6. Window Management
V-Sync
# WRONG
DisplayServer.vsync = DisplayServer.VSYNC_ENABLED
# CORRECT
DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_ENABLED)
DPI / Content Scaling
# WRONG (size_scale does not exist)
get_window().size_scale = 2.0
# CORRECT
get_window().content_scale_factor = 2.0
# OR for base resolution:
get_window().content_scale_size = Vector2i(1920, 1080)
Window Size (Integer Required)
# Adjust physical window size inversely proportional to scale
var target_size := Vector2i((LOGICAL_BASE_SIZE / scale).ceil())
window.size = target_size
Deferred Window Size (Avoid Startup Race)
window.content_scale_factor = scale
var target_size := Vector2i((LOGICAL_BASE_SIZE / scale).ceil())
window.set_deferred("size", target_size)
Borderless Window
get_window().borderless = true
DisplayServer.window_set_flag(DisplayServer.WINDOW_FLAG_BORDERLESS, true)
Resize Flag (Windows DWM)
# WRONG (RESIZE flag does not exist as a set-to-true operation)
DisplayServer.window_set_flag(DisplayServer.WINDOW_FLAG_RESIZE, true)
# CORRECT (pass false to RESIZE_DISABLED to ENABLE resizing)
DisplayServer.window_set_flag(DisplayServer.WINDOW_FLAG_RESIZE_DISABLED, false)
Borderless Resize (Global Mouse)
# Must use global screen coordinates for borderless resize
var global_mouse := DisplayServer.mouse_get_position()
var window_top_left := get_window().position
var target_width: int = int(max(global_mouse.x - window_top_left.x, 800))
var target_height: int = int(max(global_mouse.y - window_top_left.y, 500))
get_window().size = Vector2i(target_width, target_height)
Cursor Shapes
DisplayServer.cursor_set_shape(DisplayServer.CURSOR_DRAG)
DisplayServer.cursor_set_shape(DisplayServer.CURSOR_HSIZE)
DisplayServer.cursor_set_shape(DisplayServer.CURSOR_ARROW)
Always on Top
get_window().always_on_top = true
Window Position
DisplayServer.window_set_position(Vector2i(x, y))
# OR
get_window().position = Vector2i(x, y)
7. Audio
Bus Management
AudioServer.add_bus()
AudioServer.set_bus_volume_db(index, db)
AudioServer.set_bus_mute(index, true)
AudioServer.get_bus_count()
AudioServer.get_bus_name(index)
Audio Effects
var capture := AudioEffectCapture.new()
AudioServer.add_bus_effect(bus_index, capture)
AudioServer.set_bus_effect_enabled(bus_index, effect_index, true)
Microphone Input
var mic_stream := AudioStreamMicrophone.new()
player.stream = mic_stream
player.bus = AudioServer.get_bus_name(bus_index)
player.autoplay = true
PCM Playback
var wav := AudioStreamWAV.new()
wav.format = AudioStreamWAV.FORMAT_16_BITS
wav.mix_rate = 48000
wav.stereo = true
wav.set_data(PackedFloat32Array)
player.stream = wav
player.play()
Input Device List
if AudioServer.has_method("get_input_device_list"):
var devices: Array[String] = AudioServer.call("get_input_device_list")
8. Networking (Multiplayer)
ENet Multiplayer
var peer := ENetMultiplayerPeer.new()
var err: int = peer.create_server(port)
# OR
var err: int = peer.create_client("192.168.1.10", 8443)
multiplayer.multiplayer_peer = peer
Signals
multiplayer.connected_to_server.connect(_on_connected)
multiplayer.connection_failed.connect(_on_failed)
multiplayer.server_disconnected.connect(_on_disconnected)
multiplayer.peer_connected.connect(_on_peer_joined)
multiplayer.peer_disconnected.connect(_on_peer_left)
multiplayer.get_peers() # Array[int]
multiplayer.get_unique_id() # int
UDP (VoIP)
var pkt_peer := PacketPeerUDP.new()
pkt_peer.connect_to_host(ip, port)
pkt_peer.put_data(PackedByteArray)
pkt_peer.get_available_packet_count()
pkt_peer.get_packet() # PackedByteArray
pkt_peer.close()
Local Addresses
# WRONG
IP.get_network_interfaces() # as static call
# CORRECT (on the singleton instance)
var addresses: Array = IP.get_local_addresses()
var interfaces: Array = IP.get_network_interfaces()
Crypto (AES-256)
var aes := AESContext.new()
var hash_ctx := HashingContext.new()
hash_ctx.start(HashingContext.HASH_SHA256)
hash_ctx.update(key_bytes)
var key: PackedByteArray = hash_ctx.finish()
aes.start(AESContext.MODE_ECB_ENCRYPT, key)
var encrypted: PackedByteArray = aes.update(data)
aes.finish()
9. File I/O
ConfigFile
var config := ConfigFile.new()
config.load("user://settings.cfg")
var val = config.get_value("section", "key", default)
config.set_value("section", "key", value)
config.save("user://settings.cfg")
FileAccess
# Plain
var file := FileAccess.open(path, FileAccess.READ)
var text := file.get_as_text()
file.close()
# Compressed (FastLZ)
var file := FileAccess.open_compressed(path, FileAccess.WRITE, FileAccess.COMPRESSION_FASTLZ)
file.store_string(json_str)
file.close()
# Check existence
FileAccess.file_exists("user://data.json")
CSV
file.store_csv_line(["col1", "col2", "col3"])
var row: PackedStringArray = file.get_csv_line()
JSON
var parsed: Variant = JSON.parse_string(json_text)
var json_str: String = JSON.stringify(data, "\t")
10. Resources & Loading
Loading Scripts
# Static (compile-time)
const MyScript = preload("res://scripts/my_script.gd")
# Dynamic (runtime)
var script: GDScript = load("res://scripts/dynamic.gd")
var instance: Node = script.new()
Loading Fonts
var custom_font: Font = load("res://font/text_with_emojis.tres")
Class Registration
class_name MyEngine
extends Node
Note: If two scripts reference each other (circular dependency), relax the type hint to the base class to break the loop:
# Instead of: var tab_bar: MainTabBar # Use: var tab_bar: TabBar
11. Performance
FPS
Engine.get_frames_per_second()
Engine.max_fps = 60
Draw Calls
Performance.get_monitor(Performance.RENDER_TOTAL_DRAW_CALLS_IN_FRAME)
Low Processor Usage
OS.low_processor_usage_mode = true
Object Pooling
# Pre-allocate nodes, toggle visibility instead of create/free
var pool: Array[Label] = []
for i in POOL_SIZE:
var node := Label.new()
node.visible = false
add_child(node)
pool.append(node)
Avoid Per-Frame Allocations
# Reuse cache arrays
var _cache: Array[Node] = []
func update():
_cache.clear()
for item in items:
if item.valid:
_cache.append(item)
# Swap with working array
var temp = items
items = _cache
_cache = temp
Debounce Timers
var _save_timer := Timer.new()
_save_timer.wait_time = 1.0
_save_timer.one_shot = true
add_child(_save_timer)
# In event handler:
_save_timer.stop()
_save_timer.start()
12. Style Conventions
File Naming
| Pattern | Purpose |
|---|---|
*_engine.gd |
Logic / data / network layer |
*_ui.gd |
Presentation / node construction |
*_helper.gd |
Static utility functions |
Engine Pattern
extends Node
class_name MyEngine
signal data_changed(value: Variant)
const CONFIG_PATH := "user://my_settings.cfg"
var _state: Dictionary = {}
func _ready() -> void:
_load()
func _exit_tree() -> void:
_save()
func _process(delta: float) -> void:
# Per-frame logic
# Public API
func get_value(key: String) -> Variant: ...
func set_value(key: String, val: Variant) -> void: ...
# Private helpers
func _load() -> void: ...
func _save() -> void: ...
UI Pattern
extends RefCounted
class_name MyUI
static func setup_ui(page_container: Node, engine: Node) -> void:
# Guard against double-setup
if page_container.has_node("MyWrapper"):
return
# Locate content area
var layout_vbox = page_container.get_child(0)
var main_content = layout_vbox.get_child(0)
# Clear previous
for child in main_content.get_children():
child.queue_free()
# Build UI...
Print Statements
print("[EngineName] Message: ", detail)
push_warning("[EngineName] Non-fatal issue")
push_error("[EngineName] Fatal issue: ", err)
13. Common Pitfalls & Corrections
| # | Wrong | Correct | Reason |
|---|---|---|---|
| 1 | var items := [] |
var items: Array[Label] = [] |
Typed arrays required |
| 2 | DisplayServer.vsync = true |
DisplayServer.window_set_vsync_mode(...) |
API changed in 4.x |
| 3 | Input.is_touchscreen_available() |
DisplayServer.is_touchscreen_available() |
Moved to DisplayServer |
| 4 | Color.rand() |
Color(randf(), randf(), randf()) |
No static rand() |
| 5 | yield(...) |
await ... |
yield removed in Godot 4 |
| 6 | container.clear() |
for c in container.get_children(): c.queue_free() |
No clear() on Container |
| 7 | style.border_width_all = 2 |
Per-side border_width_left/right/top/bottom |
No _all shortcut |
| 8 | style.corner_radius_all = 16 |
Per-corner corner_radius_top_left/... |
No _all shortcut |
| 9 | StyleBoxFlat.new(style) |
style.duplicate() |
No copy constructor |
| 10 | fit_content_width = true |
wrap_mode = TextEdit.LINE_WRAPPING_BOUNDARY |
Property renamed |
| 11 | Slider.new() |
VSlider.new() / HSlider.new() |
Slider is abstract |
| 12 | tab_bar.tabs_closeable = true |
tab_bar.tab_close_display_policy = ... |
Property renamed |
| 13 | get_window().size_scale = 2.0 |
get_window().content_scale_factor = 2.0 |
Property renamed |
| 14 | DisplayServer.window_set_flag(RESIZE, true) |
DisplayServer.window_set_flag(RESIZE_DISABLED, false) |
Flag semantics inverted |
| 15 | dialog.get_cancel() |
dialog.get_cancel_button() |
Method renamed in 4.6+ |
| 16 | Node.get_process_delta_time() |
get_process_delta_time() |
Instance method, not static |
| 17 | vec.ceiled() |
Vector2i(vec.ceil()) |
Wrong method name |
| 18 | IP.get_network_interfaces() (static) |
IP.get_network_interfaces() (on singleton) |
Must be instance call |
| 19 | NOTIFICATION_WM_QUIT_REQUEST |
NOTIFICATION_WM_CLOSE_REQUEST |
Renamed in Godot 4 |
| 20 | timer.pause_mode = PAUSE_MODE_PROCESS |
timer.process_mode = PROCESS_MODE_ALWAYS |
Renamed in Godot 4 |
| 21 | Engine.has_feature("web_view") |
OS.has_feature("web") |
Correct singleton + feature |
| 22 | var child := control.create_item(root) |
var child: TreeItem = control.create_item(root) |
Type inference fails |
| 23 | Engine.get_process_time() |
Does not exist | Use Time.get_ticks_msec() |
| 24 | NOTIFICATION_FOCUS_OUT |
NOTIFICATION_APPLICATION_FOCUS_OUT |
Correct constant name |
Quick Reference: Godot 4.7 Class Hierarchy (UI)
CanvasItem
├── Control
│ ├── Container
│ │ ├── VBoxContainer
│ │ ├── HBoxContainer
│ │ ├── GridContainer
│ │ ├── MarginContainer
│ │ ├── PanelContainer
│ │ ├── ScrollContainer
│ │ ├── TabContainer
│ │ ├── HSplitContainer / VSplitContainer
│ │ └── CenterContainer
│ ├── Label
│ ├── Button
│ ├── CheckBox
│ ├── OptionButton
│ ├── LineEdit
│ ├── TextEdit
│ ├── RichTextLabel
│ ├── HSlider / VSlider
│ ├── ProgressBar
│ ├── SpinBox
│ ├── ColorPickerButton
│ ├── Tree
│ ├── TabBar
│ ├── PopupMenu
│ └── ConfirmationDialog (extends Window)
├── Node2D
├── AudioStreamPlayer
└── Node (base)
References
- Godot 4.7 Documentation
- Godot 4.7 Class Reference
- Getting Started
- Tutorials
- Engine Details
- GDScript Syntax
- Signals
- Multiplayer
- Window Management
- Audio
Last updated for Godot 4.7. Cross-reference with AIFIX.md for project-specific corrections.
Feature inventory (stable, in-progress, missing) 5-phase roadmap (stabilization → multi-PC power features → multi-monitor & AI → packaging → platform expansion) Non-functional requirements Success metrics Immediate next steps Verified content is consistent with README.md, SUMMARY.md, and AITASKS.md.
31687 94 Task Ready for Review
Agent
Power Tools
Manual
Guided
Auto
EnvoyOS — Feature Roadmap
EnvoyOS is a multi-PC, multi-monitor productivity dashboard built in Godot 4.6. It combines clipboard sync, AI assistance, timers, inventory, web tab sessions, media controls, theme building, and a modular tab/engine architecture into a single desktop app.
The roadmap below prioritizes:
- Stabilizing the existing core product.
- Completing the highest-value multi-PC productivity features.
- Expanding AI and developer workflows.
- Building a polished, cross-platform desktop suite.
1. Feature Inventory
1.1 Implemented / Stable
| Feature | Module(s) | Notes |
|---|---|---|
| Cross-PC clipboard sync | clipboard_engine.gd |
ENet + AES-256, client/server, auto-reconnect |
| AI chat | ai_engine.gd, ai_ui.gd |
Ollama HTTP streaming, multi-turn context |
| Markdown editor | editor_engine.gd, editor_ui.gd |
Autosave, debounce, theme-aware typography |
| Timer panel | time_engine.gd, time_ui.gd |
Up to 12 timers, background tick, config persistence |
| Inventory tracking | inventory_engine.gd, inventory_ui.gd |
Slot-based UI, expiration tracking |
| Tab management | tab_engine.gd, tab_ui.gd |
Custom tab bar, session persistence |
| Theme builder | ui_engine.gd, ui_ui.gd |
bg/font/accent/shade/transparency, light/dark adaptivity |
| Video / display settings | video_engine.gd, video_ui.gd |
FPS, DPI, window mode, position, size |
| OS grid layout | grid_engine.gd, grid_ui.gd |
Configurable rows × columns dashboard |
| User AFK detection | user_active_engine.gd |
Cross-platform idle time |
| System tray | systemtray.gd |
Minimize-to-tray, app-level tray integration |
| Ship game | ship_engine.gd, ship_ui.gd |
2D physics mini-game, object pooling |
| Web tab session viewer | web_engine.gd, web_ui.gd |
Loads Tab Session Manager JSON, tree view |
| VoIP scaffolding | voip_engine.gd, voip_ui.gd |
UDP, VAD, jitter buffer; still in progress |
| Audio settings | audio_engine.gd, audio_ui.gd |
Master volume, mute, mic level |
1.2 Partial / In Progress
| Feature | Module(s) | Missing |
|---|---|---|
| System monitor | system_engine.gd, system_ui.gd |
Empty; needs CPU/RAM/disk/GPU stats and dashboard UI |
| AI code assistant | ai_code_engine.gd, ai_code_ui.gd |
Empty; needs code-gen, review, explain, diff view |
| Music / media player | music_engine.gd, music_ui.gd |
extends Node only; needs playback queue, seeking, shuffle |
| VoIP | voip_engine.gd, voip_ui.gd |
Needs input validation, UI polish, reliability tests |
| OS grid layout | grid_engine.gd, grid_ui.gd |
Broken references to clipboard engine state |
1.3 Stubs / Missing
| Feature | Notes |
|---|---|
| Command palette | No Ctrl+K action search |
| Global hotkeys | No OS-level hotkey registration |
| Monitor layout memory | No per-monitor window position/size persistence |
| Floating overlays | No always-on-top widgets |
| P2P file transfer | No chunked ENet file streaming UI |
| Shared workspace / Kanban | No cross-PC synced task board |
| Notification mirroring | No cross-PC toasts |
| Clipboard history ring buffer | No searchable past clipboard picker |
| Snippet library | No saved snippets with sync |
| Screen recording / capture | No per-monitor GPU capture |
| Voice commands | No local voice-to-action pipeline |
| In-app updater | No auto-update mechanism |
| Unit tests / CI | No GUT test suite or automated checks |
2. Roadmap
Phase 1 — Stabilize & Finish Core Gaps (Weeks 1–3)
Goal: make every existing tab useful and remove obvious technical debt.
| # | Feature / Task | Description |
|---|---|---|
| 1.1 | System monitor | Implement system_engine.gd (CPU %, RAM, disk, GPU, network) and system_ui.gd dashboard with live gauges. |
| 1.2 | Music player | Build music_engine.gd playback queue, seeking, shuffle/repeat; wire music_ui.gd. |
| 1.3 | AI code tab | Implement ai_code_engine.gd for explain/refactor/generate and ai_code_ui.gd with side-by-side editor + diff view. |
| 1.4 | Fix grid engine refs | Replace is_connected_to_network / is_reconnecting with valid clipboard engine properties. |
| 1.5 | Input validation | Validate VoIP port, timer/inventory hh:mm:ss fields, and all numeric inputs. |
| 1.6 | Merge duplicate engines | Consolidate tab_engine.gd and top_engine.gd into one base class. |
| 1.7 | Testing foundation | Add GUT unit tests for theme serializer, tab engine, timer, inventory, clipboard crypto. |
| 1.8 | CI pipeline | GitHub Actions: godot --headless --check-only + GUT on push. |
| 1.9 | Bug bash | Systematic pass over known technical debt list. |
Exit criteria: No empty tabs, no broken references, CI green, basic test coverage.
Phase 2 — Multi-PC Power Features (Weeks 4–7)
Goal: make EnvoyOS the primary productivity bridge between the user’s PCs.
| # | Feature | Description |
|---|---|---|
| 2.1 | Clipboard history ring buffer | Last 100 text/image items with timestamp, source PC, app name. Searchable picker. |
| 2.2 | Snippet library | Save frequently used snippets/templates, sync to all peers, one-click paste. |
| 2.3 | Cross-PC clipboard search | Type keyword → RPC search across all peers’ history. |
| 2.4 | P2P file transfer | Drag-and-drop file onto network tab → AES-256 → chunked ENet stream → progress bar. |
| 2.5 | Shared workspace folder | Synced directory with chunked transfer; edit on one PC, open on another. |
| 2.6 | Shared Kanban board | To-Do / In-Progress / Done, RPC-synced in real time; cards with title, tags, due date, assigned PC. |
| 2.7 | Notification mirroring | Broadcast system/app notifications to all peers; timer expiry → alert all PCs. |
| 2.8 | Command palette | Ctrl+K fuzzy-search actions: tabs, theme, timers, connections, mute, etc. |
| 2.9 | Global OS-level hotkeys | Clipboard history, screenshot, quick paste, show/hide app. |
| 2.10 | Offline queue | Queue clipboard/file/task changes while disconnected; auto-sync on reconnect. |
Exit criteria: User can copy/paste, transfer files, and track tasks across PCs without leaving EnvoyOS.
Phase 3 — Multi-Monitor & AI (Weeks 8–11)
Goal: exploit multi-monitor setups and deepen AI assistance.
| # | Feature | Description |
|---|---|---|
| 3.1 | Monitor layout memory | Detect monitors, save per-monitor window positions/sizes, restore on launch. |
| 3.2 | Floating overlays | Always-on-top widgets: timer countdown, clipboard dock, AFK glow, assigned to monitors. |
| 3.3 | Window tiling / snapping | Ctrl+1..4 quadrant, Ctrl+Shift+Left/Right half, named layouts. |
| 3.4 | Per-monitor contrast | Different shade/brightness per monitor; auto time-of-day adjustment. |
| 3.5 | AI code review | Explain selected code, refactor with diff, generate from prompt, review PR diff. |
| 3.6 | AI meeting notes | Stream mic audio → local Whisper/Ollama → bullet summary → sync to peers. |
| 3.7 | Cross-PC input mirroring | Type on PC-A → keystrokes stream to PC-B; relative mouse deltas; per-peer toggle. |
| 3.8 | AI suggestion engine | Context-aware “Smart Suggestions” on clipboard/tab/editor. |
| 3.9 | Screenshot + AI annotate | Capture region → send to local model → written description. |
Exit criteria: Multi-monitor setup feels native; AI handles coding and documentation tasks with low friction.
Phase 4 — Polish, Packaging & Release (Weeks 12–14)
Goal: ship a polished, discoverable, cross-platform product.
| # | Feature | Description |
|---|---|---|
| 4.1 | Auto-start with OS | Windows registry, Linux .desktop, macOS LaunchAgent. |
| 4.2 | System tray menu | Show, Mute, Disconnect, Open Timer, Open Kanban, Exit. |
| 4.3 | Backup / restore | Export/import all user:// settings as .zip. |
| 4.4 | Accessibility pass | High-contrast mode, font scaling, screen-reader labels. |
| 4.5 | Session replay | Record short UI interaction replay for bug reports. |
| 4.6 | User documentation | Full user manual, keyboard shortcut reference, per-OS setup guide. |
| 4.7 | Export / packaging | Release builds for Windows / Linux / macOS; in-app updater (optional). |
| 4.8 | Localization scaffold | String externalization for future i18n. |
| 4.9 | Error reporting | Opt-in crash/telemetry with minimal PII. |
Exit criteria: Stable release builds, onboarding experience, accessible UI.
Phase 5 — Differentiators & Platform Expansion (Ongoing)
Goal: make EnvoyOS a complete second OS layer for power users.
| # | Feature | Description |
|---|---|---|
| 5.1 | Screen recording per monitor | GPU capture, “record this monitor” toggle, export to file. |
| 5.2 | Voice commands | “Hey, paste the last email” → VAD → snippet match → paste. |
| 5.3 | Pomodoro focus mode | 25/5 preset; auto-pauses AI streaming and sync during focus. |
| 5.4 | Dark-light auto-switch | Theme tied to system clock or ambient light. |
| 5.5 | Peer presence bar | Avatar strip showing online PCs via multiplayer.get_peers(). |
| 5.6 | Shared timer countdown | Timer on PC-A visible live on PC-B; sync end_timestamp. |
| 5.7 | Calendar view | Timers/inventory deadlines on mini monthly calendar. |
| 5.8 | CSV export | Timer/inventory state to CSV. |
| 5.9 | Low-stock alerts | Inventory item below threshold → red flag on all PCs. |
| 5.10 | Browser extension | Sync tabs/bookmarks/clipboard from browser to EnvoyOS. |
| 5.11 | Mobile companion | System-tray / applet / browser-extension views; remote status. |
| 5.12 | Plugin / extension system | Load custom GDScript panels into the tab grid. |
| 5.13 | App manifest / RBAC | JSON manifest for per-panel permissions, resource limits, data classification. |
| 5.14 | Power management UI | Per-tab/applet status, bandwidth throttle, CPU/GPU allocation. |
3. Non-Functional Requirements
| Area | Requirement |
|---|---|
| Performance | Inactive engines must not consume CPU; active tabs < 10 ms frame budget on mid-range hardware. |
| Reliability | Network sync must survive peer disconnects and auto-reconnect without data loss. |
| Security | All network payloads encrypted (AES-256); no secrets in logs; configurable trust model. |
| Accessibility | WCAG-conscious contrast, full keyboard navigation, font scaling, screen-reader labels. |
| Portability | Windows, Linux, macOS desktop; optional WebAssembly build for applet view. |
| Testability | Unit tests for engines, integration tests for network sync, smoke tests for UI tabs. |
| Observability | Structured logs, error reporting, session replay for debugging. |
| Packaging | Signed release binaries per platform; in-app updater (optional). |
4. Success Metrics
| Metric | Target |
|---|---|
| Core stability | Zero empty tabs; no broken references; CI green |
| Cross-PC utility | User can copy, transfer, and track work across ≥ 2 PCs without external tools |
| AI value | ≥ 3 AI-powered workflows (chat, code review, meeting notes) usable daily |
| Multi-monitor | ≥ 3 per-monitor layout features working out of the box |
| Release readiness | Signed builds for 3 platforms; documented onboarding; < 5 min setup time |
| Extensibility | Third-party panels can be loaded via manifest / plugin API |
5. Immediate Next Steps
- Complete Phase 1 items (system monitor, music player, AI code tab, grid refs, tests, CI).
- Pick one Phase 2 multi-PC feature to prototype (clipboard history or P2P file transfer).
- Define the JSON manifest schema for panels / RBAC (informs Phase 5 but can be designed now).
- Establish a release cadence and changelog process.