| Age | Commit message (Collapse) | Author |
|
M9 (task wt0): GEGL quick-enhance (optional, feature-gated).
- src/enhancer.{c,h}: 8 built-in presets (Auto-fix, Brightness, Contrast,
Saturation, Warm, Cool, Sharpen, Denoise) using gegl:stretch-contrast,
gegl:exposure, gegl:brightness-contrast, gegl:saturation, gegl:color-enhance,
gegl:unsharp-mask, gegl:noise-reduction. enhancer_apply via buffer-source +
buffer-sink pipeline; enhancer_export via gegl:jpg-save. User graph presets
deferred (NOT_SUPPORTED in v1).
- meson: gegl_dep conditional (-DHAVE_GEGL); enhancer.c in libggae.
- tests: unit test_enhancer (presets dims+non-zero, export file written).
LSAN suppressions for GEGL third-party init/node leaks.
- 20/20 green, ASan clean (with GEGL lib suppressions).
|
|
M8 (partial): plain-C operate modules + unit tests.
- src/mover.{c,h}: move files to configured dests (g_file_move + stem
collision suffix -1/-2), one-level undo (move back), acts on marks-or-current.
- src/opener.{c,h}: %f expand + detached GSubprocess launch (non-blocking).
- src/runner.{c,h}: /bin/sh -c with single-quoted %f/%d (injection guard),
async wait_check, rescan on completion.
- src/clipboard.{c,h}: image/png via GdkContentProvider + uri-list.
- meson: new sources added to libggae.
- tests: unit test_mover (move/undo/collision), test_opener (true/false/weird
filenames), test_runner (true/false/injection guard). 19/19 green, ASan clean.
- Fixed: g_str_replace (not in GLib 2.88) → manual _str_replace; GdkContentProvider
API (new_typed/new_for_value); mover collision stem-suffix; runner /bin/sh -c
argv (single arg, not shell-parsed); test GFile leaks.
|
|
M6: progressive low-res preview via libjpeg-turbo.
- jpeg.c: two-phase decode (1/8 low-res via progress_cb, full via GdkPixbuf
with EXIF orientation). Feature-gated.
- loader.h/c: LoaderProgressCb + load_progressive on backend struct;
loader_load_async accepts progress params; JPEG special-cased in task thread.
- window.c: _load_progress_cb marshals partial to main thread.
- tests: test_loader_jpeg (progress + dims), test_progressive_jpeg (sample).
16/16 green, ASan clean.
|
|
M5 (task qt0): modern format backends.
- src/loader/backends/jxl.c: libjxl decoder → RGBA → GdkMemoryTexture.
- src/loader/backends/heif.c: libheif decoder → interleaved RGBA →
GdkMemoryTexture (also handles AVIF when libavif is absent).
- src/loader/backends/avif.c: libavif decoder (written; auto-disabled when
libavif is not installed).
- src/loader/loader.c: BACKENDS array conditionally includes the backends
via -DHAVE_JXL/HAVE_AVIF/HAVE_HEIF; pixbuf remains last (fallback).
- meson.build: conditional backend sources + c_args + extra deps.
- tests: assert_unsupports updated to assert NULL only (any error, works
with or without the specific backend compiled in).
- Minimal build (all disabled) + full build (jxl+heif) both 14/14 green,
ASan clean.
|
|
M4 (task ot0): fullscreen + slideshow + info overlay.
- src/info.{c,h}: plain-C EXIF gather via libexif (camera, lens, focal,
aperture, shutter, ISO, datetime, orientation) + file info (dims, format,
size). info_format() produces the overlay text. Unit-tested.
- src/window.c: fullscreen toggle (f), slideshow (S, 3s auto-advance), info
overlay (i, GtkLabel in GtkOverlay, auto-hide 5s), Esc contextual back
(fullscreen→unfullscreen→large→grid→quit). Stack wrapped in GtkOverlay.
ggaze_window_get_stack() accessor for tests.
- src/shortcuts.c: f, S, i, Escape bindings.
- meson.build: libexif dep added.
- tests: unit test_info (EXIF extraction + orientation tag). 14/14 green,
ASan clean.
|
|
M7 (task jt0): thumbnail cache + grid view + trash.
- src/trash.{c,h}: ./Trash bin (lazy create, collision suffix -1/-2, restore_last
for u undo, permanent delete for D). Plain-C, unit-tested.
- src/thumbnail.{c,h}: freedesktop TMS cache (normal/large/x-large buckets,
md5(URI) key, Thumb::URI/MTime/Size, mtime verify). Async GTask worker with
GCancellable so the grid can cancel pending requests on dispose.
- src/gridview.{c,h}: GgazeGrid (GtkWidget wrapping GtkFlowBox): one cell per
navigator file, lazy thumbnail decode on realize, +/- resize 64-512, dim
removed cells, mark badges, Enter/double-click → 'activate' signal, cursor
sync. ggaze_grid_detach() breaks the nav ref before dispose.
- src/navigator.{c,h}: removed set (mark_removed/is_removed/unmark_removed);
_relist preserves removed items (stay dimmed even if absent from disk);
get_remaining = count - removed_count.
- src/window.c: d (trash+mark_removed+advance), D (permanent delete, confirm
dialog if >1 marked), u (restore_last+rescan), t (toggle grid/large),
+/- (viewer zoom in large / grid resize). Header counter uses remaining.
- src/shortcuts.c: d/D/u/t/+/= bindings added.
- tests: unit test_trash + test_thumbnail, integration test_grid_cull
(grid count + trash/undo + ./sample-images 613-cell grid). 13/13 green,
ASan clean.
Fixed: grid dispose UAF (detach before nav freed), g_file_get_basename leak,
test p_plain lifetime, thumbnail async-cancellable for clean dispose, test
grid-toggle avoiding async-thumbnail drain issues.
|
|
M3 (task it0): responsive + prefetch.
- src/loader/loader.{c,h}: loader_load_async/_finish wrap the sync
loader_load in a GTask worker; the task's source object is the GFile so the
finish callback can check it against navigator.current (last-write-wins).
- src/texturecache.{c,h}: bounded LRU (cap 4) of GFile->GdkTexture, hash+GQueue
O(1) get/put, entry owns the key (no double-unref), evicts LRU on overflow.
- src/window.c: async visible load (cancel-then-recreate single GCancellable);
cache hit -> show+prefetch, miss -> async load; last-write-wins guard
(g_file_equal(current, loaded)) before showing; prefetch next/prev into the
cache via a separate prefetch cancellable (cancelled each round). Finish
callbacks hold a ref on the window (released in the callback) so they never
deref a freed window; dispose cancels both cancellables.
- tests: unit test_texturecache (LRU evict/order/replace/miss), integration
test_responsive_nav (10 rapid next -> last-write-wins). M1/M2 tests updated
to pump the main loop (loads are now async) + drain_main before exit.
- meson: texturecache in libggae; GIO_USE_VFS=local in fixtures_env.
Fixed: texturecache double-unref (key ownership), async-callback UAF (window
ref in callback data + drain in tests), a test g_build_filename leak. Sub-agent
review issues addressed (dispose + cache-hit cancel in-flight loads).
|
|
M2 (task ht0): walk the directory.
- src/navigator.{c,h}: GObject (no GtkWidget) holding the folder listing —
image-MIME filter + extension fallback, hide-RAW-sidecars (decision #33),
sort name/time/size, cursor + prev/next/wrap, path-based marks (survive
re-sort, pruned on rescan/monitor, cleared on remove), nearest-fallback,
GFileMonitor debounced 250ms (decision #28), 'changed' signal. 92% gcov.
- src/shortcuts.{c,h}: GtkShortcutController (MANAGED) binding h/l/Left/Right/
g/G/o/q to win.* actions; one table all milestones add to.
- src/window.{c,h}: open file->parent+current / folder->first; h/l/g/G actions
-> nav -> load -> viewer; single GCancellable last-write-wins (g_file_equal
guard for M3 async); header title 'filename · n/total'; GtkDropTarget
(GDK_TYPE_FILE_LIST, many->first, decision Z); GtkFileDialog open.
- data/gschema: add hide-raw-sidecars key.
- tests: unit test_navigator (12 subtests), integration test_walk_folder
(temp fixtures walk + ./sample-images skip-if-absent). 8/8 green; ASan clean
(GIO_USE_VFS=local in fixtures_env).
Fixed during this milestone: a UAF (borrowed c_name used after unref), a
cancellable double-ref leak, time::modified UINT64 vs INT64, and several
test-harness leaks. Sub-agent review issues addressed (mark pruning on
rescan/monitor, remove-before-cursor test, recursive temp-dir cleanup,
callback _cb naming, connect-before-mutate).
|
|
M1 (task gt0): show one image with zoom/pan.
- src/loader/detect.{c,h}: magic-byte format sniffing (JPEG/PNG/GIF/WebP/
TIFF/ICO/JXL/AVIF/HEIF) -> GgazeFormat; pure, no I/O, unit-testable.
- src/loader/loader.{c,h}: loader_load() sniffs the header and dispatches to
the first registered backend; GgazeLoaderBackend struct; pixbuf is the
fallback (last, accepts UNKNOWN). M1 ships only the pixbuf backend.
- src/loader/backends/pixbuf.c: GdkPixbufLoader decode +
gdk_pixbuf_apply_embedded_orientation (decision #26) -> GdkTexture via
gdk_memory_texture_new (avoids the deprecated gdk_texture_new_for_pixbuf).
- src/viewer.{c,h}: GgazeViewer : GtkWidget custom widget (decision #31) —
fit/100%/in/out zoom, cursor-centered zoom, drag-to-pan with clamping,
dark background, GtkSnapshot render nodes.
- src/window.c: open -> loader_load -> viewer_set_texture -> stack 'large'.
- tests: unit test_detect (13 cases) + test_loader_pixbuf (plain/rotated-EXIF
8x4 orient6->4x8/png/rgba/missing/unsupported jxl-avif-heif/corrupt),
integration test_open_and_show (fixture + rotated + ./sample-images
skip-if-absent). 6/6 green; detect 97% / loader 91% / pixbuf 87% coverage.
- fixtures: gen.py produces plain.jpg, rot6.jpg, small.png, rgba.png.
- AGENTS.md: documents the ./sample-images optional test corpus convention.
Sub-agent review fixes: use-after-free of c_name in ggaze_window_open
(BLOCKER), gtk_stack_get_pages leak in test_window (BLOCKER), coverage gap,
dead branch, viewer measure, _prefix/_cb naming, include order, extern in
header, pan clamp, stale comments — all addressed.
|
|
M0 (task ft0): the app shell — no feature views yet (those are M1 large, M7
grid).
- src/main.c: g_application_run + --version via handle-local-options (--help
is GApplication's own); exits before activate so --version works headless.
- src/app.{c,h}: GgazeApp : AdwApplication : GtkApplication (libadwaita
styling, decision #29), single-instance (decision #32),
G_APPLICATION_HANDLES_OPEN; activate + open reuse the active window
(replace-on-new-open); ggaze_window_open remembers the GFile.
- src/window.{c,h}: GgazeWindow : GtkApplicationWindow with AdwHeaderBar and a
GtkStack (children 'grid'/'large', default 'grid'); dispose clears the file.
- data/org.buetow.ggaze.desktop (image/* MIME, validated) and
data/org.buetow.ggaze.gschema.xml (all keys + defaults, --strict clean).
- meson: libggaze static lib shared by the executable and tests; install to
bindir/applications/glib-2.0 schemas; gnome.post_install compiles schemas +
updates desktop db.
- tests: unit test_app (--version/--help/unknown via subprocess), integration
test_window (offscreen stack checks + open-titles), bootstrap. 3/3 green.
- docs/tech-stack.md: window-geometry shape corrected to (width,height,
fullscreen,maximized) to match the schema.
|
|
Phase 0 (task et0): lays the build/test/CI/convention groundwork before any
feature code.
- meson.build: project ggaze C11 -Wall -Wextra; deps gtk4/glib/gio/libadwaita;
feature options gegl/jxl/avif/heif (auto); generated ggaze-config.h with
app id, version, GGAZE_HAVE_* feature flags.
- tests: GLib g_test harness with separate unit/integration suites
(--suite unit / --suite integration); test_bootstrap sanity test proves
the harness compiles and the config header is reachable; -Db_coverage=true
coverage target wired.
- CI: .woodpecker/ci.yml three lanes (minimal, gegl, asan) with xvfb for the
integration track and a coverage lane that warns until M10.
- conventions: .clang-format (3-space, 80-col, K&R, *-on-var, return type on
its own line) + .editorconfig matching docs/coding-conventions.md; CI
clang-format --dry-run -Werror gate.
- top-level README.md (humans) and AGENTS.md (agents) pointing into docs/.
- LICENSE (GPL-3.0-or-later), .gitignore, dir placeholders.
Acceptance: meson setup build && ninja -C build && meson test -C build green;
clang-format --dry-run --Werror clean.
|