1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
|
# Tech Stack
## Core
| Concern | Choice | Notes |
|----------------|-------------------------------------------|-----------------------------------------|
| Language | C (C11) | `-Wall -Wextra`, no glib-style boilerplate beyond what GTK needs |
| UI toolkit | GTK4 | custom viewer widget; `GtkDropTarget` for file/folder drops |
| Native look | libadwaita (decided) | GNOME styling; see decision #29 |
| Object/async | GLib / GObject / GTask | bundled with GTK |
| Config | GSettings + Gio | schema `org.buetow.ggaze` *(placeholder)* |
| Build | Meson + Ninja | GNOME/Fedora standard |
| Packaging | RPM (Fedora), optional Flatpak | AppStream metainfo |
| Conventions | c-best-practices skill | see [coding-conventions.md](coding-conventions.md) |
| Image proc. | GEGL + babl *(optional, feature-gated)* | quick enhance, color mgmt, export copy; see [gegl.md](gegl.md) |
| Clipboard | GdkClipboard + GdkContentProvider | `image/png` (pixels) and `text/uri-list` (files) for `Ctrl+c` |
://| Folder watch | GFileMonitor (GIO) | live auto-refresh of the current dir (external adds/deletes) |
| Testing | `meson test` + gcov/lcov | ≥80% on plain-C modules; see Testing below |
| Quality audit | `auditing-code-quality` skill | at milestones (C: c-best-practices + find-code-bugs + SOLID + beyond-SOLID) |
## Image decode
Tiered: a sniffed-format dispatcher selects a backend; GdkPixbuf is the
fallback for anything common it already supports.
- **GdkPixbuf** (fallback) — PNG, JPEG, GIF (animated via `GdkPixbufAnimation`),
WebP, TIFF, ICO.
- **libjpeg-turbo** (optional, direct) — faster JPEG + progressive first-scan
low-res preview.
- **libjxl** — JPEG XL.
- **libavif** — AVIF.
- **libheif** — HEIF / HEIC (and AVIF via libheif if libavif absent).
- **libpng** — only if GdkPixbuf path is insufficient (unlikely first cut).
- **GEGL loaders** (optional, if GEGL enabled) — `gegl:jpg/png/tiff/webp/…-load`
can augment the enhance/export path and bring ICC-aware decode; JXL/AVIF/HEIF
still need their own libs.
- **EXIF orientation** — every backend honors the EXIF Orientation tag so the
decoded texture is upright (GdkPixbuf: `gdk_pixbuf_apply_embedded_orientation`;
others read the tag via libexif and rotate/flip). Manual rotate/straighten
compose on top; `s` export resets the tag to "normal" (1) to avoid
double-rotation in other apps.
Each backend behind a `GgazeLoaderBackend` struct:
`gboolean (*can_load)(const guint8 *head, gsize len);`
`GdkTexture *(*load)(GFile *file, GCancellable *, GError **);`
Backends compiled in conditionally via meson `feature` options so a minimal
build (GdkPixbuf only) is possible.
## Progressive preview (low-res first)
For large/slow images, show a quick low-res or progressive scan before the full
frame is decoded. Concretely: libjpeg-turbo can yield a downscaled scan after
reading only the header + a few MCU rows; libjxl supports partial decode. The
loader API should allow streaming a low-res `GdkTexture` first, then replacing
it. Treat as a later milestone, but design the loader signature for it now.
## Thumbnail cache
- freedesktop Thumbnail Managing Standard: `~/.cache/thumbnails/normal`
(128×128) and `large` (256×256); shared with other compliant apps.
- Store PNG with `Thumb::URI`, `Thumb::MTime`, `Thumb::Size` keys.
- Verify mtime before trust; re-decode on mismatch.
## Settings keys (GSettings schema `org.buetow.ggaze`)
- `sort` — enum: name / capture-time / size
- `wrap` — bool: wrap at folder ends
- `background` — enum: black / dark / grey / checker
- `scroll-behavior` — enum: zoom / pan-when-zoomed / navigate
- `slideshow-delay` — double (seconds)
- `thumbnail-size` — int: grid thumbnail pixel size (resizable via `+`/`-`)
- `hide-trashed` — bool
- `window-geometry` — `(iiib)` (width, height, fullscreen, maximized); persisted, restored on launch
- `destinations` — `a(ss)`: ordered array of `(name, path)` pairs for the
`m` move popup. List order determines auto-assigned hotkeys (`1`, `2`, …).
Edited via the Preferences dialog (`,`) or `gsettings`.
- `editors` — `a(ss)`: ordered array of `(name, command)` pairs for
the `e` open-in-external popup. Commands use freedesktop `Exec` placeholders
(`%f` = current file path); e.g. `gimp %f`, `identify %f`. Launched detached
via `GSubprocess` (GLib), so ggaze stays responsive.
- `scripts` — `a(ss)`: ordered array of `(name, command)` pairs for
the `!` run-script popup. Run **asynchronously** through `/bin/sh -c`
(pipes/redirection/`~/` work). Placeholders: `%f` = current image path,
`%d` = current folder (paths single-quoted against injection). On exit,
ggaze rescans the directory.
- `enhance-presets` — `a(ss)`: ordered array of `(name, gegl-graph)` pairs
for the `a` enhance popup (GEGL only). Order = auto-assigned hotkey order.
Ships with built-in defaults.
## External deps (Fedora package names, approximate)
```
gtk4-devel glib2-devel libadwaita-devel (?)
gdk-pixbuf2-devel
libjpeg-turbo-devel libjxl-devel libavif-devel libheif-devel
gegl-devel babl-devel # optional, feature-gated
meson gcc
```
## Testing
- Unit tests for the plain-C modules — `navigator`, `detect`, `thumbnail`,
`mover`, `opener`, `runner`, `enhancer`, `trash`, `settings` (no GTK/display
needed); run with `meson test`.
- **Coverage target ≥80%** on those modules, measured with `gcov`/`lcov`
(`meson setup -Db_coverage=true && meson test && ninja -C build coverage`).
A coverage gate in CI rejects drops below 80%.
- GTK-side: `gtk-test`-style smoke tests where useful; mostly manual.
- A small `tests/fixtures/` image set covering each supported format.
|