Skip to content

Latest commit

 

History

History
163 lines (134 loc) · 7.4 KB

agent-context.md

File metadata and controls

163 lines (134 loc) · 7.4 KB

AI Agent Context — Animura

Purpose: Fast-loading reference for AI coding agents. Read this before operating on the codebase.

Quick Reference

Project Identity

  • Name: Animura — modular live wallpaper engine for Windows
  • Language: C++20 (host), TypeScript + React 18 (frontend)
  • Build: CMake 3.30+, MSVC 2022, static CRT (/MT), Vite 5
  • Platform: Windows only (Win32 + WebView2 + Qt 6)

Key Directories

Animura/
├── src/                    # Host C++ sources (includes desktop integration)
├── include/animura/        # Host headers (includes desktop integration)
├── frontend/               # React + TypeScript UI (Vite)
│   └── src/
│       ├── components/     # React components
│       ├── bridge/         # C++ ↔ JS communication layer
│       ├── hooks/          # Custom React hooks
│       └── theme/          # CSS design tokens
├── modules/                # Wallpaper plugin DLLs
└── resources/              # .qrc (icon only), .rc, icon.ico

Module sources live separately at E:/coding/C/live-wallpaper-modules/<name>/.

Key Classes

Class File Role
WallpaperController src/WallpaperController.cpp Central orchestrator — module lifecycle, worker thread
WebView2Host src/WebView2Host.cpp Embeds Edge WebView2 in a QWidget, hosts React UI
NativeBridge src/NativeBridge.cpp COM IDispatch object — JS ↔ C++ communication
ModuleCatalog src/ModuleCatalog.cpp Scans /modules for module.json files
ModuleLibrary src/ModuleLibrary.cpp Wraps LoadLibraryEx/FreeLibrary/GetProcAddress
IWallpaperModule include/animura/IWallpaperModule.hpp ABI interface: run(), stop(), hwnd(), virtual dtor
ZipExtractor src/ZipExtractor.cpp ZIP read & extract via minizip-ng (module installation)
SettingsSchemaValidator src/SettingsSchemaValidator.cpp Recursive JSON validation against schema
AppSettings src/AppSettings.cpp Application-scoped persistent settings (singleton)
Paths src/Paths.cpp Standardized filesystem paths via SHGetKnownFolderPath
DesktopHostResolver src/DesktopHostResolver.cpp Probes shell window hierarchy for the correct WorkerW/Progman host
WallpaperHost src/WallpaperHost.cpp RAII-style Attach(hwnd)/Detach() for wallpaper module windows
SystemWallpaperBridge src/DesktopWallpaper.cpp Saves/restores static desktop wallpaper via COM + registry fallback
Win32Utils src/Win32Utils.cpp Free functions for window style sanitization, geometry, transparency

Critical Architecture Rules

  1. Rendering library thread affinity is LAW.

    • Module initialization, window creation, context management, rendering, and teardown must all happen on the same thread.
    • This applies to common libraries: GLFW (glfwInit/glfwTerminate), OpenGL contexts, Direct3D devices, Vulkan instances.
    • The worker thread (m_worker) is the rendering thread. The Qt main thread is NOT.
    • Never call rendering functions from the main thread.
  2. Module lifecycle contract:

    • createModule(json) → run() → stop() → ~IWallpaperModule()
    • stop() only sets an atomic flag. It does not block or join.
    • run() is a blocking call — it returns only after stop() signals exit.
    • Module destruction MUST happen on the same thread that created it.
  3. DLL loading order:

    • LoadLibraryEx → GetProcAddress("createModule") → call factory → store result
    • m_module.reset() destroys the module object
    • FreeLibrary happens later in ModuleLibrary::unload()
    • DLL must NOT be freed while any code inside it is executing.
  4. WebView2 threading:

    • WebView2 callbacks arrive on arbitrary threads.
    • NativeBridge::Invoke() marshals all calls to the Qt main thread via QMetaObject::invokeMethod with Qt::BlockingQueuedConnection.
    • PostWebMessageAsJson is thread-safe.
  5. COM bridge return types:

    • All NativeBridge methods return BSTR (COM string). JS receives strings, not numbers.
    • getRunningModuleId() returns a string like "0" — must be parsed with Number() in JS.
    • Strict equality (===) between string and number always fails.

Threading Quick Reference

Main Thread (Qt):        UI events, WallpaperController public methods,
                         WebView2Host, NativeBridge marshaling,
                         m_module->stop(), m_module->hwnd()
WebView2 Threads:        Managed internally — callbacks arrive here,
                         marshaled to main thread via NativeBridge
Worker Thread (m_worker): Module creation, rendering init, window creation,
                         mainLoop(), rendering, teardown, module destruction

Module Interface (ABI Contract)

class IWallpaperModule {
public:
    virtual ~IWallpaperModule() = default;
    virtual void run() = 0;
    virtual void stop() = 0;
    virtual HWND hwnd() const = 0;
};

extern "C" __declspec(dllexport) IWallpaperModule* createModule(const char* settingsJson);

What NOT to Assume

  • ❌ Do NOT assume m_module.reset() is safe to call from any thread
  • ❌ Do NOT assume rendering libraries are thread-safe (many have strict thread affinity)
  • ❌ Do NOT assume stop() blocks until run() returns
  • ❌ Do NOT assume the module DLL is unloaded immediately after m_module.reset()
  • ❌ Do NOT assume rendering objects can be deleted without a current context
  • ❌ Do NOT assume all modules use the same CRT or compiler settings
  • ❌ Do NOT assume all modules use the same rendering library
  • ✅ The host uses static CRT /MT — verify modules match

Build Commands

# Frontend (run before C++ build)
cd frontend && npm run build

# C++ — configure
cmake --preset vs2022-x64

# C++ — build
cmake --build build --config Release

Runtime Flow Summary

App Start → main.cpp → QApplication → WebView2Host (creates QWidget + WebView2)
  → WallpaperController (ctor scans /modules)
  → Paths::init() → AppSettings::initialize() (loads %LOCALAPPDATA%/Animura/settings.json)
  → (if restoreLastWallpaper) find module by lastUsedWallpaperID → startWallpaper(idx)
  → WebView2 navigates to frontend/dist/index.html (production) or localhost:5173 (dev)
  → React renders module grid via NativeBridge.GetModulesList()

User clicks module card → SettingsPanel slides in
User clicks Start:
  → NativeBridge.StartWallpaper(idx) [COM → Qt main thread]
  → validate → ensureLibraryLoaded (LoadLibraryEx)
  → startWorker → spawn thread
    → createModule(json) → m_module.reset(ptr)
    → m_wallpaperHost->Attach(hwnd)
    → m_module->run() [BLOCKS]
  → emit runningModuleChanged → PostWebMessageAsJson → React updates button

User clicks Stop:
  → NativeBridge.StopWallpaper() [COM → Qt main thread]
  → m_wallpaperHost->Detach()
  → m_module->stop() [sets atomic, returns immediately]
  → m_worker.join() [waits for run() to return]
  → m_module.reset() [destroys module]
  → restoreWallpaper()

User clicks Add Module:
  → NativeBridge.InstallModule() [COM → Qt main thread]
  → QFileDialog → WallpaperController::installModuleFromPath(path)
  → ZipExtractor validates, reads module.json in-memory, extracts
  → ModuleCatalog::addModule() → emit modulesChanged()
  → React auto-refreshes module grid