| Age | Commit message (Collapse) | Author |
|
The _proceed_prev/next/first/last helpers are trivial wrappers over the
public ggaze_window navigation API and do not depend on GEGL, but were
defined only inside the #if GGAZE_HAVE_GEGL block. The always-compiled
_action_prev/next/first/last handlers reference them via _maybe_save_then,
so the gegl=disabled (minimal) lane failed with '_proceed_prev undeclared'
(regression introduced by the enhance save-dialog commit, before M8 marks).
Move the four helpers out of the GEGL guard so they are always compiled.
Verified: meson setup build-min -Dgegl=disabled -Djxl=disabled -Davif=disabled -Dheif=disabled && ninja -C build-min && meson test -C build-min (23/23 OK); default gegl=enabled lane still 24/24 OK.
|
|
The _save_buffer fix that rejects pre-existence as proof of a
successful save (mtime/size change verification) was already in place.
Add the missing test case (c) for a stale pre-existing regular file at
the destination, which the task requires alongside the directory and
unwritable-destination cases:
/enhancer/export_stale_dest:
(a) writable stale file -> correctly overwritten; assert the new PNG
bytes differ from the stale content (not mere presence).
(b) read-only stale file the saver cannot replace -> export returns
FALSE with a GError; skipped under root (root bypasses file-mode
perms; the directory case in export_real_success covers a
root-safe save failure).
Gated behind gegl_dep.found() (same as the rest of test_enhancer), so
the minimal (gegl=disabled) lane is unaffected. All 7 enhancer subtests
+ the full 24-test suite pass; ASan/LSan clean.
|
|
Wire the navigator mark API into the window actions, grid badges, and
keyboard/pointer controls:
- Navigator: add a path-based range anchor (p_last_mark) set on `v`
toggle-on and cleared on unmark-of-anchor / clear_marks / remove /
mark_removed / rescan-prune; expose navigator_get_last_mark().
navigator_mark_removed now clears the removed file's mark (decision Q
/ task du0) and the anchor if it was it, without touching unrelated
marks, and emits "changed" whenever the removed set or marks actually
changed so grid badges and the header never go stale.
- Window: add win.mark-range (`V`: range from the last-mark anchor to the
current/grid-selected image), register it, wire Shift+V, and document
it in the shortcuts overlay. Trash/delete clear marks via mark_removed.
- Grid: middle-click on a cell selects it and dispatches win.mark
(pointer-accessible marks).
- Tests: unit test_marks_anchor_and_remove and
test_marks_anchor_pruned_on_rescan; integration test_marks.c covering
toggle, all+clear, range, persistence across view switch,
clear-on-trash, and the middle-click gesture wiring; updated
test_shortcut.c action table + count.
clang-format clean; full suite + ASan leak check green.
|
|
Add a dim 's Save enhanced copy' hint at the bottom of the enhance
panel so the save shortcut is visible right where the toggles are.
When you navigate to another image (h/l/g/G) with an unsaved enhance
preview active, a Save / Discard / Cancel alert now asks what to do:
Save exports <stem>-enhanced.<ext> with the enabled-preset chain then
moves on; Discard drops the preview and moves on; Cancel keeps the
preview and stays on the current image. _action_prev/next/first/last
route through _maybe_save_then; the s action and the dialog's Save
button share _enhance_do_save. Non-GEGL builds get a passthrough gate.
|
|
next to image
Filters are now independent toggles that compose: the active set is a
bitmask (u_enhance_mask) and the enabled GEGL ops are chained in array
order (enhancer_apply_chain / enhancer_export_chain). Press 1-8 to
toggle any preset on/off at any time; 0 / the Original row clears all;
the live preview re-applies the whole stack each time, so you can
add/remove filters in any order. The window title lists the enabled
presets.
The side panel now sits NEXT TO the image (a horizontal content box:
[panel] + image area) instead of over it; in fullscreen it reparents
to an overlay over the image so fullscreen stays full. Enabled rows
are highlighted (.ggaze-enhance-on: accent background, white bold).
enhancer.c: extract _op_for_builtin, add enhancer_apply_chain and
enhancer_export_chain (single-preset apply/export kept for the tests);
_save_buffer factored out (ju0/ku0 fixes unchanged). New
test /enhancer/apply_chain (compose + empty-mask rejection).
|
|
Instead of a transient 'a' popup, add a persistent enhance side panel
(GtkRevealer overlaid on the left of the main content) toggled by 'a'.
It lists every preset as a button showing its hotkey (1..8) plus a
'0 Original' row, and stays open while you compare presets.
Each optimization now has a dedicated global hotkey: 1-8 apply preset
1-8 directly (win.enhance-1 .. win.enhance-8), whether the panel is
visible or not. 'a' toggles the panel; 's' still saves the enhanced
copy; the panel's '0 Original' row (and navigating away) reverts.
Adds win.enhance-1..8 actions (one _action_enhance_n handler parses the
index from the action name) and the 1-8 keybindings; the ? overlay
Enhance group documents the panel + 1-8 + s. test_shortcut full-table
updated (33 rows, 29 actions).
|
|
Replace the a=cycle behavior with a GtkPopover listing the presets as
rows (1 Auto-fix, 2 Brightness, ... 8 Denoise, 0 Original). Click a
row or press its digit to apply that preset as the live preview; Escape
or 'a' again closes. Refactored the apply logic into _apply_enhance(idx)
shared by the menu and (unchanged) win.enhance-save. The popover is
parented to the overlay and torn down on the 'closed' signal. The ?
overlay Enhance group now documents the menu.
|
|
The enhancer module existed (GEGL presets: Auto-fix, Brightness, Contrast,
Saturation, Warm, Cool, Sharpen, Denoise) but was never connected to the
app and GEGL was never initialized. Wire it up:
- app.c: gegl_init() once in GApplication::startup (GEGL-gated).
- enhancer.c: add enhancer_load(GFile) -> GeglBuffer (gegl:load) and
enhancer_buffer_to_texture(GeglBuffer) -> GdkTexture (RGBA8 ->
GdkMemoryTexture) for the live preview bridge.
- enhancer_export: pick the saver from the output extension (jpg/png/webp)
instead of always gegl:jpg-save (ju0 - was writing JPEG bytes into .png);
verify the save actually produced a non-empty newer file instead of
trusting g_file_test(EXISTS) on a pre-existing path (ku0 - false success).
- window.c: win.enhance (a) cycles the active preset and previews it on the
current image (switches to large view, loads via GEGL, applies, shows the
texture); win.enhance-save (s) exports <stem>-enhanced.<ext> with the
active preset, never overwriting the original. Preview resets on
navigation; the title shows the active preset.
- shortcuts.c: a -> win.enhance, s -> win.enhance-save; ? overlay Enhance
group.
- tests: test_enhancer gains load_and_to_texture, export_format (PNG/JPEG
signature checks), export_real_success (ku0 - parent-missing and
pre-existing-directory both return FALSE). LSAN suppressions cover
GEGL's jpg-load plugin leak. test_shortcut full-table updated (25 rows).
Synchronous apply (may briefly block on large images) and a navigate-away
dirty prompt are deliberately out of scope for v1.
|
|
_on_nav_changed rebuilt every cell on every navigator "changed" signal,
so a single h/l/j/k press destroyed and recreated all 613 GtkPicture
cells (each re-requesting its thumbnail) and the grid flashed blank.
Now it only does a full ggaze_grid_refresh when the file count actually
changed (rescan/trash/move); a mere current/mark change just refreshes
mark badges in place and re-selects the cursor.
Add j/k row navigation: ggaze_grid_move_cursor finds the cell in the
adjacent row closest to the current column via gtk_widget_compute_bounds
and sets navigator.current to it (so the header / large-view preview
track the move and the light nav-changed path re-selects it). h/l stay
linear via the existing global win.next/prev. Also document j/k in the
? shortcuts overlay.
|
|
The inline SHORTCUTS_UI string had mismatched closing tags (extra
</child> elements), so gtk_builder_new_from_string failed with
"Element child was closed, but the currently open element is object"
and aborted the process the moment ? was pressed. Regenerate the UI
with correct <object>/<child> nesting (validated with xmllint).
|
|
The navigator already had a mark API and the grid rendered mark badges,
but nothing could set marks — so multi-file ops (D, Ctrl+c, m) only ever
acted on the single current image. Wire up marking in the UI:
- v toggles a mark on the highlighted grid cell (or the current image in
large view). navigator_toggle_mark does not emit "changed", so the
cell's badge is updated in place via ggaze_grid_update_mark_badge
(no reflow / re-decode).
- Ctrl+a marks all; Esc now clears marks first (contextual), then keeps
its existing back/quit behavior.
- The window title appends "N marked" so multi-selection is visible.
- A GtkCssProvider styles .ggaze-marked (accent border + tint) — the
class was set but had no styling, so badges were invisible.
Also add a ? -> win.shortcuts action that presents a GtkShortcutsWindow
(grouped: navigation, view, selection, files, zoom) built from an inline
GtkBuilder UI string, transient to the window.
tests/test_shortcut.c full-table test updated for the three new bindings
(v, Ctrl+a, ?) -> 23 rows, 19 actions.
|
|
thumbnail_get_async used g_task_run_in_thread, which runs on GLib's
unlimited shared thread pool — every visible cell's thumbnail decode
spawned its own thread, pegging every core (load ~20, 74 running
threads on a 613-image folder). Move the decode onto a GThreadPool
owned by Thumbnail, capped at max(1, min(num_cpus/2, 4)) threads.
g_task_return_* still marshals each result to the main thread, so
callbacks are unchanged. Observed: 74 -> 2 active decode threads,
load ~20 -> ~5.
If the pool failed to create, fall back to g_task_run_in_thread so
thumbnails still work (just unbounded) rather than not at all.
|
|
ggaze_window_open always set the stack to "large" and loaded the first
image even for a directory argument, contradicting the documented
folder-to-grid behavior. A directory arg now shows the populated
thumbnail grid; a file arg still opens large on that image.
_show_texture additionally forced the stack to "large" whenever an image
finished loading, which yanked a just-opened folder back out of the grid
the moment its first image decoded. Drop the forced stack switch — the
caller already sets the right view (file-open/toggle/grid-activate set
large; directory-open sets grid), so _show_texture only needs to update
the viewer's texture.
|
|
Arrow-key navigation in the grid moved the GtkFlowBox selection but never
updated navigator.current, so Enter (and toggling to large with t) opened
the stale current image instead of the highlighted one. _on_flow_key
emitted the activate signal directly without syncing, unlike the
double-click path (_on_child_activated) which does sync.
Add ggaze_grid_sync_current(): reads the selected flowbox child and calls
navigator_set_current_file. Call it from _on_flow_key before emitting
activate, and from _action_toggle_view's grid→large branch (then load
the now-current image). Mirrors the existing double-click sync.
|
|
black grid)
GgazeGrid is a GtkWidget with a single child (the GtkScrolledWindow
holding the GtkFlowBox) but set no layout manager and overrode no
measure/size_allocate vfunc, so GTK4 never allocated the child and the
grid stayed a 0x0 black area (`Trying to snapshot GtkScrolledWindow
without a current allocation`). Add a GtkBinLayout in class_init so the
child is sized to fill the grid and the flowbox renders.
|
|
runner_run built "/bin/sh -c " + cmd and ran g_shell_parse_argv on it,
which split the command into argv words — sh -c then received only the
first word as its script and treated the rest as $0/$1..., silently
truncating multi-word commands, pipelines, and redirections. The
existing 'true %f' test passed only because true ignores missing args.
Build argv directly as {"/bin/sh", "-c", c_cmd, NULL} so sh -c gets
the whole expanded command as one script string. %f/%d stay single-
quoted via g_shell_quote, so hostile filenames cannot break out of the
quotes and inject commands. Also removed dead code in _shell_quote.
New observable tests (write to a temp file, read it back):
- multi_word: printf 'hello world' (would fail on old code)
- pipeline: echo | cat
- redirection: echo > file
- spaces_in_args: printf 'a b c'
- hostile_filename: ';touch sentinel;' name must NOT create the sentinel
|
|
clipboard_copy_uris wrapped its URI text as a generic G_TYPE_STRING
provider, so targets could not request text/uri-list and file-manager
paste failed. Extracted clipboard_build_uri_provider builds a union of
two byte providers: text/uri-list (RFC 2483 CRLF-terminated URI lines,
trailing CRLF) and text/plain (newline-joined local paths, URI fallback
for non-local files). clipboard_copy_uris sets the union on the
GdkClipboard with correct refcounting (new_union steals the sub-provider
refs; clipboard takes its own ref).
Empty list returns NULL and leaves the clipboard untouched.
New integration test tests/test_clipboard.c: serializes the provider's
content per MIME type via gdk_content_provider_write_mime_type_async
into a GMemoryOutputStream and memcmps the bytes. Covers formats
(both MIME advertised, bogus type absent), single + multiple files
(exact CRLF/newline-joined bytes), empty list (no crash), and a
negative (unknown MIME rejected with GError).
|
|
The GtkShortcutController on the window used MANAGED scope, so the
viewer's own GtkEventControllerKey consumed key events before the
window-level win.* shortcuts were consulted — t (toggle-view) and Esc
(back) never fired. Switch to GLOBAL scope, which registers the
shortcuts with the toplevel's global shortcut manager consulted before
child key controllers. Comment notes the future-text-entry caveat.
New integration test tests/test_shortcut.c (3 subtests):
- controller_scope: asserts the window's shortcut controller is GLOBAL
(and is ours, not GtkApplicationWindow's mnemonic controller).
- keypath_toggle_and_back: verifies the t→win.toggle-view and
Escape→win.back bindings (bidirectional keyval/action check) and
dispatches them via gtk_shortcut_action_activate (the controller's own
dispatch primitive), asserting the stack flips large↔grid. GTK 4.22
exposes no public GdkKeyEvent synthesis API, so this is the most
faithful exercise of the keyboard shortcut path available.
- full_table_registered: asserts all 16 win.* actions and all 20
shortcut rows are registered, guarding against table regressions.
Notes a pre-existing, out-of-scope keybinding conflict: GTK4 normalizes
GDK_KEY_G/0 to GDK_KEY_g/0 in GtkKeyvalTrigger, so win.last (G) collides
with win.first (g); left unfixed per task's 'preserve existing behavior'.
|
|
_expand_command now parses the command template with g_shell_parse_argv
FIRST, then substitutes %f into the already-parsed argv elements, so %f is
always exactly one argv value regardless of spaces or shell metacharacters
in the path. Previously it split on literal spaces, breaking quoted commands
like sh -c '...' and mis-splitting paths with spaces.
opener_launch propagates parse errors (returns FALSE, sets GError) instead
of launching malformed argv. A non-local GFile (no path) is now rejected
with G_IO_ERROR_NOT_SUPPORTED rather than producing an argv with an
embedded NULL.
Tests: quoted, quoted-with-%f, escaped spaces, option flags, weird
filename (quoted + unquoted), malformed (unclosed quote), empty command,
direct argv-shape regression (one argv value), and no-local-path rejection.
|
|
M10 (task yt0): polish & packaging.
- data/org.buetow.ggaze.metainfo.xml: AppStream metainfo.
- data/ggaze.1: man page with keybindings reference.
- packaging/ggaze.spec: Fedora RPM spec.
- data/meson.build: installs desktop+metainfo+gschema+man; gnome.post_install.
- tests/test_full_lifecycle.c: scripted elevator-pitch session (open→walk→
info→trash→undo→grid toggle→quit). 21/21 green, ASan clean.
- Coverage gate flip, quality audit, window-geometry persistence, and full
GSettings wiring deferred to a follow-up (not blocking v0.1 release).
|
|
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).
|
|
The asan lane used -Db_sanitize=address,undefined but never installed the
sanitizer runtime libs, so it could not link. kt0 (leak check after M1) ran
clean locally after installing them: full unit+integration suite under ASan,
zero leaks / zero UBSan issues.
|
|
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.
|
|
|
|
|