# mcap A bare [Dear ImGui](https://github.com/ocornut/imgui) (docking branch) desktop application written in [Jai](https://jai.community), for Windows, macOS and Linux: the window, the frame loop and everything around them, ready for an app to be written into `src/app/app_ui.jai`. ## What you get - **Native rendering per platform.** Win32 + Direct3D 11 on Windows; SDL3 + SDL_GPU (Metal on macOS, Vulkan on Linux) elsewhere. On Windows the window comes up on a WARP device in a few milliseconds while the hardware device is created on a thread, then moves across; a lost device is recovered. - **Docking and multi-viewport ImGui**, with a default layout (`assets/data/default_imgui.ini`) and a Reset Layout hotkey. - **One instance.** Launching the app again brings the running one forward, over a shared memory channel (a file lock decides the owner on macOS and Linux). - **Tray / menu bar.** Closing or minimizing hides the window to the tray; the tray icon brings it back or quits. - **Hotkeys** from a `[hotkeys]` section of the settings file, and **global hotkeys** (`[global_hotkeys]`: Windows and macOS) that work while another app is in front. - **A settings file** (`settings.config`) that is written with every key listed, reloaded as soon as it is saved, and merged rather than overwritten when the app saves to it. - **Themes**: built-ins, 36 shipped `.theme` files, and a theme editor that saves new ones. - **Logging** through a multi-producer, single-consumer queue to a per-version log file and an in-app Event Log, plus crash reports. - **Debug panels**: frame times, memory, renderer, fonts (Ctrl+Shift+D, Ctrl+Shift+1). - **DPI awareness**, live resize that keeps drawing, CJK font fallback from the system fonts. - **Background shutdown**: closing hands the remaining work to a thread while the window shows progress, so it never looks hung. - **Asynchronous tests**, compiled into debug builds and run with `run_tests`. ## The capture viewer `src/app/viewer.jai`, `catalog.jai`, `ocr_ui.jai` and `labels_ui.jai`: every `.mcap` in `D:\SnagitIndex\mcap` as a grid of previews, and one capture at full size in the Capture window. - **Search** above the grid: every word must match, anywhere in the OCR text, window title, URL, app name, annotation text or labels; `"a phrase"` matches as one; `ocr:`, `title:`, `url:`, `app:`, `note:` and `label:` limit a word to one field. `like:"a circuit board"` searches by meaning: the captures whose image is nearest the phrase, best first (the label model's text encoder makes the phrase's vector; each labelled capture's image vector is in its file). In the Capture window the matching OCR lines are highlighted, with Next and Previous. Matching runs on a pool of background threads (`app/background.jai`), as do the Stats window's numbers, so typing never waits on them; the grid shows the last results until the new ones are back, usually within a frame. - **Order:** newest first by capture date, once the catalog (what search needs from every file) has loaded; by name until then. - **OCR** (`modules/OCR`): the OCR window shows whether it runs on the GPU and, if not, what to install, and switches model (kept as `model` under `[ocr]` in the settings file). **Scan the library** reads every capture shown that the model has not read yet, storing each reading in its file as it goes, so it can pause, cancel and pick up again; with **Labels too** it labels them in the same pass. For testing, the OCR window's **Erase OCR readings** and the Labels window's **Erase labels and vectors** take them out of every file in the library again (after asking); `-erase ocr|labels|all` does the same without the UI and exits. The Capture window's **OCR** button reads one capture; a capture read by several models keeps each reading, and the window can show any of them. `-library FOLDER` shows another folder of `.mcap` files; `-scan` scans it with the chosen model once it has loaded and exits (0, or 1 if any capture failed), for a scan without the UI, such as on a copy. - **Delete:** hovering a thumbnail shows a trash button in its corner. The confirmation offers moving the file to the Recycle Bin (on Linux and macOS, to a `deleted` folder in the library folder) or deleting it permanently: overwritten with random bytes, flushed to the disk, renamed and deleted. Copies elsewhere (the source `.snagx`, backups, synced versions) are untouched, and an SSD may keep old blocks. The choice, and "don't ask again", last until the app restarts. Not while OCR is running. - **Refresh** above the grid looks at the folder again: captures whose file was deleted leave the grid, and new files are added (each capture keeps its index, as the workers, the OCR scan and Stats refer to captures by index). By hand for now; File_Watcher can call `viewer_refresh` later. - **Stats** above the grid: what OCR found in the captures shown, by the chosen model: captures with text, lines, words, characters, confidence, words per capture, a year table, the captures with the most words (click to open) and the commonest words (click to search). `-stats` logs the same numbers once the catalog has loaded, and exits. `-search QUERY` starts with QUERY in the search box (with `-stats`, the numbers are for its matches, and the first ten are logged). Stats also counts each capture's best label (click one to search for it). - **Labels** (`modules/Labels`, SigLIP 2 base at 384 px): each capture is scored against a label list, `labels.config` in the app's data folder (`%APPDATA%\mcap`): a name and a prompt per label, a fuller description scoring better than a word. A starter list is written the first time; edits are picked up while the app runs. A scan stores each capture's five best labels and its image vector in its file (`labl` and `embd` chunks, see `modules/Capture_File/README.md`), so a changed list is scored again from the stored vectors without reading the images. The Labels window shows the list and how much of the library is labelled; the Capture window shows the capture's labels. `label:` matches a capture's best label, and any scoring at least half as much. ## Building Requires the Jai compiler, beta 0.2.030 or later. ``` jai first.jai # debug build jai first.jai - release # release build jai first.jai - full_release # debug and release jai first.jai - release compile_tests # release build with the test suite ``` Binaries are written to `bin/`, with `assets/` copied beside them. | Platform | Output | Notes | | --- | --- | --- | | Windows | `bin/mcap-debug.exe`, `bin/mcap.exe` | The release build gets the icon and a manifest, and no console. | | macOS | `bin/mcap-macos-debug`, `bin/mcap.app` | Apple silicon (arm64) only, with a native `jai`. The release build is an ad hoc signed `.app`. | | Linux | `bin/mcap-linux-debug`, `bin/mcap-linux` | Needs SDL3 installed (`libSDL3.so`). | `jai check_targets.jai - linux` type-checks the Linux build from any OS without linking it. The macOS check only works on a Mac, since the Objective-C module runs code at compile time. Run any build with `run_tests` to run the test suites (debug builds include them), `-allow-multiple-instances` to skip the single-instance check, or `-version`. For unattended runs (scripts, CI, agents), add `-headless`: the app exits once the tests finish, with exit code 0 if every suite passed and 1 otherwise, and a failed assertion or runtime check is written to stderr, the log and the crash report and exits with 1 instead of opening a message box (`runtime_support_headless` in `modules/Runtime_Support.jai`): ``` bin/mcap-debug.exe run_tests -headless -allow-multiple-instances > test-output.txt ``` `jai first.jai - no_crash_dialog [...]` makes builds for automation: `NO_CRASH_DIALOG` makes them headless whatever their arguments, and everything they build goes to `bin-headless/` instead of `bin/`, so an automated build never writes over the one you run by hand. ## Starting a new app ``` jai fork.jai - [-folder ] [-bundle-id ] [-version ] [-git] [-build] jai fork.jai - ../my-cool-app "My Cool App" -git -build ``` This copies the template (without `bin/`, `.build/`, `.git/` or `fork.jai`) and sets the names below in the copy. The folder name defaults to the app name in lowercase with spaces as `-` (`my-cool-app`), the bundle identifier to `com.example.`, and the version to `0.0.0`. `-git` initializes a repository and commits the copy; `-build` makes a debug build. It also writes `.focus-config`, a [focus](https://focus-editor.dev) project with the build and run commands, which stays out of git since it holds this machine's paths. ## Making it your app 1. Name it at the top of `first.jai`, or let `fork.jai` do it: `APP_NAME` (window title, tray, macOS bundle), `APP_FOLDER_NAME` (executable and data folder; one short lowercase word), `APP_BUNDLE_IDENTIFIER` and `VERSION`. 2. Replace `assets/images/icon.png` (any square size; it becomes the `.exe`, `.app` and window icon) and the tray icons `tray_icon.ico` (Windows) and `tray_icon.png`. 3. Write the app in `src/app/app_ui.jai`: `app_ui_startup` runs once, `app_ui_frame_start` every frame before input, `app_main_window_ui` draws, and `app_ui_shutdown` runs on the shutdown thread. To add a hotkey, declare a `Hotkey` in `src/ui/imgui_keyboard.jai`, add it to `hotkey_actions_build` and `app_check_key_combinations`, bind it in both `assets/data/default_hotkeys*.config` files (the build fails if a default leaves an action unbound), and raise `APP_USER_SETTINGS_VERSION` in `src/App.jai` so existing settings files get the new default. To add a setting, add it to `App_User_Settings`, `Settings_Seen`, `settings_parse` and `app_save_user_settings_file` in `src/ui/imgui_theme_registry.jai`. ## Layout ``` first.jai build: workspaces, build constants, Windows icon/manifest, macOS .app fork.jai copy the template to a new, named project check_targets.jai type-check another OS's build src/App.jai loads everything; global state (app, platform, perf) src/app/ main loop, your UI, background shutdown src/base/ logging, fonts, debug panels, platform layers (base/platform/) src/ui/ ImGui setup, hotkeys, themes and settings, toasts src/graphics/DX11.jai Direct3D 11 device, WARP-first startup, device-loss recovery src/backends/ ImGui's SDL_GPU backend; src/freetype/: ImGui's FreeType loader assets/ fonts, icons, default hotkeys, default layout, themes modules/ Basic (with arenas), Thread, Shared_Memory_Channel, File_Watcher, Secure_Memory, XDG, sdl3, imgui-sdl, imgui-dx11-win32, imgui-source ``` ## Files at run time | File | Location | | --- | --- | | `settings.config`, `imgui.ini`, user themes | `%APPDATA%\` on Windows, `~/Library/Application Support/` on macOS, `~/.local/share/` on Linux | | Logs | `logs//` next to the executable (next to the `.app` on macOS) | | Shipped themes | `data/themes/` next to the executable | ## Platform notes - Global hotkeys are not implemented on Linux (Wayland has no API for them). A desktop shortcut that launches the executable has the same effect, since a second launch brings the running instance forward. - On XFCE the tray icon stays after the window is restored. - `Secure_Memory` (used by `Basic`'s arenas to wipe memory) is built without libsodium; see the note at the top of `modules/Secure_Memory/module.jai` if your app keeps secrets in memory. Uses https://github.com/overlord-systems/jai-imgui as a template.