How config.c and boxpreferences.c Manage Persistent User Configurations Across Platforms in ArmorPaint

ArmorPaint persists user settings via JSON-based configuration files stored in platform-specific directories, using config.c for global application preferences and boxpreferences.c for UI layout states, both leveraging the Kinc abstraction layer for cross-platform file I/O.

ArmorPaint implements a robust, platform-agnostic configuration system that preserves user data across Windows, macOS, and Linux sessions. The architecture relies on two dedicated C modules located in paint/sources/config.c and paint/sources/boxpreferences.c to handle persistent storage. These modules utilize the Kinc framework's path and file utilities to ensure settings remain consistent regardless of the underlying operating system.

Platform-Aware Storage Locations

Both configuration modules determine the appropriate filesystem location at runtime using kinc_path_user() and kinc_path_join() from the Kinc library. This abstraction eliminates hardcoded paths and ensures compliance with each platform's user data conventions.

Windows Configuration Paths

On Windows, ArmorPaint stores configuration data in the %APPDATA%\ArmorPaint\ directory. The config_init() function constructs this path by joining the result of kinc_path_user() with "ArmorPaint" and appending the filename.

  • Global config: %APPDATA%\ArmorPaint\config.json
  • UI preferences: %APPDATA%\ArmorPaint\preferences.json (managed by boxpreferences.c)

macOS and Linux Configuration Paths

Unix-like platforms follow the XDG Base Directory specification and macOS application support conventions:

Platform Configuration Directory File
macOS ~/Library/Application Support/ArmorPaint/ config.json
Linux ~/.config/armorpaint/ config.json

The same kinc_path_join() logic applies, with the directory component adjusted per platform during compilation.

JSON-Based Serialization Architecture

Both modules use a lightweight JSON parser (json.h/json.c) to serialize and deserialize configuration data. The schema includes a version field ("version": 2) to enable safe migration of older configuration files when the application updates.

Saving Configuration Data

The config_save() and boxpreferences_save() functions populate JSON objects with current state values and write them using kinc_file_save_bytes():

void config_save(void) {
    // Build JSON object with current settings
    json_object *root = json_object_new_object();
    json_object_object_add(root, "version", json_object_new_int(2));
    json_object_object_add(root, "brush_size", json_object_new_double(brush_size));
    
    const char *json_str = json_object_to_json_string(root);
    kinc_file_save_bytes(config_path, (unsigned char *)json_str, strlen(json_str));
    json_object_put(root);
}

Loading Configuration Data

On startup, config_load() and boxpreferences_load() read existing files via kinc_file_load_bytes(), parse the JSON, and populate internal structures:

void config_load(void) {
    unsigned char *data = kinc_file_load_bytes(config_path, &size);
    if (data) {
        json_object *root = json_tokener_parse((char *)data);
        json_object *version;
        if (json_object_object_get_ex(root, "version", &version)) {
            // Migrate or load based on version number
        }
        json_object_put(root);
        free(data);
    }
}

Separation of Concerns in Configuration Modules

The codebase deliberately splits responsibilities between the two modules to maintain clean architecture:

  • paint/sources/config.c: Manages global application state including default brush parameters, recent projects list, last opened directory, and GPU backend selection. This module initializes early in main() via config_init().

  • paint/sources/boxpreferences.c: Handles dockable UI panel states specifically. It stores each panel's position, dimensions, and collapsed/expanded status. The module saves immediately when users drag or resize panels through boxpreferences_save().

Both modules expose parallel APIs:

// Global configuration API
void config_init(void);
void config_load(void);
void config_save(void);

// UI preferences API  
void boxpreferences_init(void);
void boxpreferences_load(void);
void boxpreferences_save(void);

Cross-Platform Abstraction via Kinc

All filesystem operations route through Kinc functions to ensure portability:

  • kinc_path_user(): Returns the base user data directory appropriate for the current platform
  • kinc_path_join(): Safely concatenates path components with correct directory separators
  • kinc_file_load_bytes() / kinc_file_save_bytes(): Handle raw binary I/O without platform-specific file handles

This abstraction allows identical source code to compile and execute on Windows, macOS, and Linux without conditional compilation blocks in the configuration logic itself.

Thread Safety and Error Handling

To prevent race conditions when the UI thread writes preferences while the main thread reads them, both modules protect file I/O with a global mutex:

static kinc_mutex_t config_mutex;

void config_save(void) {
    kinc_mutex_lock(&config_mutex);
    // ... file operations ...
    kinc_mutex_unlock(&config_mutex);
}

Error handling follows a defensive pattern: if kinc_file_load_bytes() returns NULL (missing file) or JSON parsing fails, the modules log the error and initialize default values. The application then writes a fresh configuration file on the next save operation, ensuring corrupted or deleted configs never crash the program.

Summary

  • config.c and boxpreferences.c in paint/sources/ implement ArmorPaint's persistent storage using JSON serialization.
  • Storage paths are determined at runtime using kinc_path_user() and kinc_path_join(), yielding %APPDATA%\ArmorPaint\ on Windows and ~/.config/armorpaint/ on Linux.
  • The system uses a versioned JSON schema (currently version 2) to support configuration migration between application updates.
  • File I/O operations are abstracted through Kinc functions (kinc_file_load_bytes, kinc_file_save_bytes) for cross-platform compatibility.
  • A global mutex (config_mutex) ensures thread-safe access to configuration files during concurrent UI and main thread operations.

Frequently Asked Questions

Where does ArmorPaint store its configuration files on Windows?

ArmorPaint stores configuration files in %APPDATA%\ArmorPaint\config.json, resolved at runtime using kinc_path_user() joined with the application name. This path appears in paint/sources/config.c where the global config_path variable is initialized during config_init().

How does ArmorPaint handle corrupted or missing configuration files?

When config_load() or boxpreferences_load() encounters a missing file or invalid JSON, the functions fall back to hardcoded default values and log the error. The application continues normally and regenerates a valid configuration file on the next save operation, ensuring user workflow interruption is minimal.

What is the difference between config.c and boxpreferences.c?

config.c manages global application settings such as brush defaults, recent projects, and hardware preferences, while boxpreferences.c specifically tracks UI panel geometry and visibility states. The former saves primarily on application shutdown, whereas the latter triggers saves immediately when users manipulate dockable panels.

Is the ArmorPaint configuration format versioned for upgrades?

Yes, both configuration modules write a "version": 2 field into their JSON output. The loading functions check this version number to determine if migration logic is required, allowing newer application versions to safely upgrade older configuration files without losing user data.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →