Compare commits

..

No commits in common. "2bd77e8121cdd03f7c039b487fd6803a947eea69" and "22cad985003c9bd269b291910564551e4caadcab" have entirely different histories.

7 changed files with 5 additions and 120 deletions

View File

@ -58,11 +58,6 @@ see [Screen capture](#screen-capture).
move the file to the Recycle Bin or to delete it permanently, overwriting it with random bytes
first; "Don't ask again" keeps the choice until the app restarts. Only the `.mcap` goes: the
`.snagx` it was converted from is untouched.
- **Copy:** click a thumbnail to select it (it is outlined, and shown in the Capture window), then
**Ctrl-C** copies its image, as the Capture window shows it (annotations and all; a video's
frame), and **Ctrl-Shift-C** its full path. Neither acts while you type in the search box. They
are `copy_image` and `copy_path` in `[hotkeys]` in `settings.config`. Copying images is Windows
only so far.
- **Files added or deleted outside the app** (in Explorer, by another program) appear in the grid
or leave it on their own, folders added, renamed or deleted too: the library is watched.
**Refresh** looks at the whole folder again, should the watcher have missed something.
@ -98,18 +93,7 @@ grid (in its order, so through a search's matches).
### The OCR window
- **Model:** PP-OCRv5 mobile (light, about 85 ms a capture on the GPU) or server (more accurate,
about 0.3 s when it runs well; see below). The choice is kept in the settings file (`model` under
`[ocr]`).
- **Known issue, the server model on the GPU:** its speed is bimodal. The same program on the same
20 captures (RTX 5080 laptop, 16 GB) runs either at about 0.27 s a capture with a peak of about
2.4 GB of process memory, or at about 1.5 s with about 6.6 GB, with no change between runs and
15 GB of the GPU's memory free beforehand. Longer runs fare worse: 50 captures peaked at 16 GB, and
100 averaged about 4.6 s a capture. The extra memory looks like GPU allocations spilling into shared
system memory, which Windows then pages: what the GPU memory defaults (onnxruntime's
kNextPowerOfTwo arena, no cuDNN max workspace, 4 workers) were meant to stop, and which they
only partly did. It is the same with Jai's allocator and with rpmalloc. Until it is fixed, scan a
large library with the mobile model. Measured on 2026-10-13 with
`ocr-bench -models ppocr-v5-server -gpu-only -limit 20`.
about 0.4 s). The choice is kept in the settings file (`model` under `[ocr]`).
- **Running on:** the GPU or the CPU, and if the CPU, what to install for the GPU.
- **Scan the library** reads every capture the model has not read yet and, with **Labels too**,
labels every capture not yet labelled with the current list. While a search is active the button

View File

@ -18,10 +18,6 @@ Ctrl-Equal increase_font_size
Ctrl-Alt-Space reset_window
Ctrl-Shift-Alt-R reset_layout
# Library: the selected capture
Ctrl-C copy_image
Ctrl-Shift-C copy_path
# Debug
Ctrl-Shift-D toggle_debug_panel
Ctrl-Shift-L toggle_logger_panel

View File

@ -18,10 +18,6 @@ Cmd-Equal increase_font_size
Cmd-Opt-Shift-Space reset_window
Cmd-Shift-Opt-R reset_layout
# Library: the selected capture
Cmd-C copy_image
Cmd-Shift-C copy_path
# Debug
Cmd-Shift-D toggle_debug_panel
Cmd-Shift-L toggle_logger_panel

View File

@ -47,7 +47,7 @@ APP_NAME :: "mcap";
APP_FOLDER_NAME :: "mcap";
GIT_BRANCH :: "check";
GIT_HASH :: "-";
VERSION :: "0.1.16";
VERSION :: "0.1.15";
BUILD_DATE :: "-";
DEBUG :: true;
COMPILE_TESTS :: true;

View File

@ -8,7 +8,7 @@ APP_NAME :: "mcap";
APP_FOLDER_NAME :: "mcap";
APP_BUNDLE_IDENTIFIER :: "com.example.mcap";
VERSION :: "0.1.16";
VERSION :: "0.1.15";
// Where everything goes: bin/, or bin-headless/ for builds made with no_crash_dialog, so automated
// builds never write over the ones you run by hand.
build_directory := "bin";

View File

@ -156,14 +156,6 @@ viewer: struct {
open_name: string;
open_result: string; // the file written, or ""; heap, the open thread's until it ends
open_error: string;
// The selected capture's image, decoded off the main thread for the clipboard (viewer_copy_image).
copy_thread: Thread;
copy_started: bool;
copy_busy: bool; // set by the main thread, cleared by the copy thread when it ends
copy_source: string; // the .mcap; heap, main thread's until the thread starts
copy_name: string;
copy_done: Decoded; // the copy thread's until it ends
}
// ---------------------------------------------------------------- startup and shutdown
@ -237,7 +229,6 @@ viewer_shutdown :: () {
for 1..viewer.workers_started signal(*viewer.wake);
for 0..viewer.workers_started - 1 thread_is_done(*viewer.workers[it], -1);
if viewer.open_started thread_is_done(*viewer.open_thread, -1);
if viewer.copy_started thread_is_done(*viewer.copy_thread, -1);
background_shutdown();
catalog_cache_save_at_shutdown(); // the workers have stopped: nothing changes the entries now
viewer_watch_stop();
@ -460,7 +451,6 @@ viewer_frame_gpu_work :: () {
viewer_apply_catalog_updates();
catalog_cache_frame(); // saves the catalog cache once it is complete and has changed (app/catalog.jai)
viewer_open_frame();
viewer_copy_frame();
segment_overlay_frame(); // the Capture window's segments: what arrived, and its texture (app/segment_ui.jai)
background_frame(); // takes back search results, and starts a search when visible_dirty (app/background.jai)
}
@ -1225,77 +1215,6 @@ viewer_open_frame :: () {
else toast_show(.Error, "Could not write % out: %", viewer.open_name, viewer.open_error);
}
// ---------------------------------------------------------------- copying the selected capture
// The selected capture: the one outlined in the grid and shown in the Capture window. -1 when none.
viewer_selected :: () -> s32 {
index := viewer.full_index;
if index < 0 || index >= viewer.captures.count || viewer.captures[index].removed return -1;
return index;
}
// Ctrl-Shift-C (copy_path in [hotkeys]): the selected capture's path, as the system writes paths.
viewer_copy_path :: () {
index := viewer_selected();
if index < 0 { toast_show(.Error, "Select a capture to copy its path"); return; }
capture := *viewer.captures[index];
path := capture.path;
#if OS == .WINDOWS path = replace(path, "/", "\\",, temp);
clipboard_set_text(path);
toast_show(.Success, "Copied the path of %", capture.name);
}
// Ctrl-C (copy_image in [hotkeys]): the selected capture's image, as the Capture window shows it
// (annotations and all; a video's frame), decoded on a thread of its own and put on the clipboard
// by viewer_copy_frame.
viewer_copy_image :: () {
index := viewer_selected();
if index < 0 { toast_show(.Error, "Select a capture to copy its image"); return; }
capture := *viewer.captures[index];
#if OS != .WINDOWS {
toast_show(.Error, "Copying images is not available on this system yet");
return;
}
if viewer.copy_started { toast_show(.Error, "Still copying %", viewer.copy_name); return; }
push_allocator(context.default_allocator);
free(viewer.copy_source); free(viewer.copy_name);
viewer.copy_source = copy_string(capture.path);
viewer.copy_name = copy_string(capture.name);
viewer.copy_done = .{};
viewer.copy_busy = true;
// Reading an image takes a few MB of temporary storage, as on the viewer's workers.
viewer.copy_started = thread_init(*viewer.copy_thread, viewer_copy_proc, temporary_storage_size = 1024 * 1024, name = "copy image");
if !viewer.copy_started { viewer.copy_busy = false; toast_show(.Error, "Could not copy %", capture.name); return; }
thread_start(*viewer.copy_thread);
}
viewer_copy_proc :: (thread: *Thread) -> s64 {
context.allocator = context.default_allocator;
defer atomic_swap(*viewer.copy_busy, false);
viewer.copy_done = viewer_decode_full(viewer.copy_source);
return 0;
}
// Each frame: puts what viewer_copy_proc decoded on the clipboard, once it is done.
viewer_copy_frame :: () {
if !viewer.copy_started || viewer.copy_busy return;
thread_is_done(*viewer.copy_thread, -1);
thread_deinit(*viewer.copy_thread);
viewer.copy_started = false;
done := *viewer.copy_done;
defer viewer_free_pixels(done);
if done.failed { toast_show(.Error, "Could not read the image of %; see the Event Log", viewer.copy_name); return; }
pixels := []u8.{cast(s64) done.width * done.height * 4, done.pixels};
if !platform_copy_image_to_clipboard(pixels, done.width, done.height) {
log_error("[viewer] Could not put the image of % on the clipboard.", viewer.copy_source);
toast_show(.Error, "Could not put % on the clipboard", viewer.copy_name);
return;
}
scaled := ifx done.width != done.source_width || done.height != done.source_height then tprint(", scaled down from % x %", done.source_width, done.source_height) else "";
log("[viewer] Copied the image of % (% x %%).", viewer.copy_source, done.width, done.height, scaled);
toast_show(.Success, "Copied % (% x %%)", viewer.copy_name, done.width, done.height, scaled);
}
viewer_grid :: () { using ImGui;
style := GetStyle();
cell := viewer.thumb_size;

View File

@ -148,9 +148,8 @@ to_string_all_bindings :: (hotkey: Hotkey) -> string {
// The bindings are read from settings.config, or from DEFAULT_HOTKEYS_DATA when the file has
// no [hotkeys] section; see hotkeys_install. once and ignore_when_text_field_active belong to the
// action and are set here. To add an action: declare its Hotkey here, add it to
// hotkey_actions_build and app_check_key_combinations, and bind it in both
// assets/data/default_hotkeys files. Settings files at APP_USER_SETTINGS_VERSION get its default
// binding at the next start (hotkeys_missing_lines); raising the version would give them "none".
// hotkey_actions_build and app_check_key_combinations, bind it in both assets/data/default_hotkeys
// files, and raise APP_USER_SETTINGS_VERSION so existing settings files get its default.
program_exit_hotkey: Hotkey;
minimize_to_tray_hotkey: Hotkey;
toggle_debug_panel_hotkey: Hotkey;
@ -163,10 +162,6 @@ increase_font_size_hotkey: Hotkey;
reset_window_hotkey: Hotkey;
reset_layout_hotkey: Hotkey;
// The selected capture (app/viewer.jai). Not while typing, so Ctrl-C in the search box copies its text.
copy_image_hotkey: Hotkey = .{ignore_when_text_field_active = true};
copy_path_hotkey: Hotkey = .{ignore_when_text_field_active = true};
Hotkey_Action :: struct {
name: string; // as written in [hotkeys]
hotkey: *Hotkey;
@ -187,8 +182,6 @@ hotkey_actions_build :: (out: *[..] Hotkey_Action) {
add(out, "increase_font_size", *increase_font_size_hotkey);
add(out, "reset_window", *reset_window_hotkey);
add(out, "reset_layout", *reset_layout_hotkey);
add(out, "copy_image", *copy_image_hotkey);
add(out, "copy_path", *copy_path_hotkey);
}
hotkey_actions: [..] Hotkey_Action;
@ -634,9 +627,6 @@ app_check_key_combinations :: () {
else toast_show(.Error, "Could not reset the window");
}
if check_key_combination(*reset_layout_hotkey) imgui_reset_layout_requested = true; // applied between frames
// The viewer is the shutdown thread's once shutdown has started.
if check_key_combination(*copy_image_hotkey) && !app_exit_shutdown_started() viewer_copy_image();
if check_key_combination(*copy_path_hotkey) && !app_exit_shutdown_started() viewer_copy_path();
if should_exit_this_frame {
log("Calling exit hotkey... exiting program!");