diff options
| author | Paul Buetow <paul@buetow.org> | 2026-07-21 18:39:47 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-07-21 18:39:47 +0300 |
| commit | cfda8297cc4432a77e3f7e8679753a908e9f5759 (patch) | |
| tree | bc8c1469989bb55656c93a65c277ab86e17adf9a /src | |
| parent | dc127b5089656944dab7ab72799d0755ca122803 (diff) | |
enhance: wire up GEGL image filters (a cycle / s save) + fix export bugs
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.
Diffstat (limited to 'src')
| -rw-r--r-- | src/app.c | 15 | ||||
| -rw-r--r-- | src/enhancer.c | 144 | ||||
| -rw-r--r-- | src/enhancer.h | 20 | ||||
| -rw-r--r-- | src/shortcuts.c | 2 | ||||
| -rw-r--r-- | src/window.c | 213 |
5 files changed, 378 insertions, 16 deletions
@@ -18,6 +18,10 @@ #include <glib.h> +#if GGAZE_HAVE_GEGL +#include <gegl.h> +#endif + struct _GgazeApp { AdwApplication parent_instance; }; @@ -41,6 +45,16 @@ ggaze_app_activate(GApplication *p_app) { } static void +ggaze_app_startup(GApplication *p_app) { + G_APPLICATION_CLASS(ggaze_app_parent_class)->startup(p_app); +#if GGAZE_HAVE_GEGL + /* Init GEGL once before any window is created (before the first enhance + * call). Idempotent if already initialised. */ + gegl_init(NULL, NULL); +#endif +} + +static void ggaze_app_open(GApplication *p_app, GFile **p_files, gint n_files, const gchar *c_hint) { (void)c_hint; @@ -62,6 +76,7 @@ ggaze_app_init(GgazeApp *p_app) { static void ggaze_app_class_init(GgazeAppClass *p_klass) { GApplicationClass *p_app_class = G_APPLICATION_CLASS(p_klass); + p_app_class->startup = ggaze_app_startup; p_app_class->activate = ggaze_app_activate; p_app_class->open = ggaze_app_open; } diff --git a/src/enhancer.c b/src/enhancer.c index b195035..aea40f9 100644 --- a/src/enhancer.c +++ b/src/enhancer.c @@ -1,5 +1,10 @@ /* enhancer.c — GEGL quick-enhance presets (optional, feature-gated). */ #include "enhancer.h" +#include "ggaze-config.h" + +#include <glib.h> +#include <glib/gstdio.h> +#include <string.h> struct Enhancer { GPtrArray *p_presets; @@ -140,38 +145,151 @@ enhancer_apply(Enhancer *e, GeglBuffer *p_in, const EnhancerPreset *p_preset, return NULL; } +/* Pick the GEGL saver op and (for jpeg) quality from the output extension. + * Returns the op name, or NULL if the extension is unsupported / the op is + * not installed. ju0: never write JPEG bytes into a .png. */ +static const char * +_saver_for_ext(GFile *p_out) { + char *c_base = g_file_get_basename(p_out); + const char *c_dot = strrchr(c_base, '.'); + const char *c_op = NULL; + if (c_dot != NULL) { + if (g_ascii_strcasecmp(c_dot, ".jpg") == 0 || + g_ascii_strcasecmp(c_dot, ".jpeg") == 0) { + c_op = "gegl:jpg-save"; + } else if (g_ascii_strcasecmp(c_dot, ".png") == 0) { + c_op = "gegl:png-save"; + } else if (g_ascii_strcasecmp(c_dot, ".webp") == 0) { + c_op = "gegl:webp-save"; + } + } + g_free(c_base); + /* webp-save ships as a plugin; only promise it if installed. */ + if (c_op != NULL && !gegl_has_operation(c_op)) { + return NULL; + } + return c_op; +} + gboolean enhancer_export(Enhancer *e, GeglBuffer *p_in, const EnhancerPreset *p_preset, GFile *p_out, GError **p_err) { (void)e; - (void)p_preset; g_return_val_if_fail(p_in != NULL, FALSE); g_return_val_if_fail(p_out != NULL, FALSE); - /* Apply the preset first. */ - GeglBuffer *p_buf = enhancer_apply(NULL, p_in, p_preset, p_err); - if (p_buf == NULL) - return FALSE; - char *c_path = g_file_get_path(p_out); if (c_path == NULL) { - g_object_unref(p_buf); g_set_error(p_err, G_IO_ERROR, G_IO_ERROR_FAILED, "enhancer: non-local export path"); return FALSE; } + const char *c_op = _saver_for_ext(p_out); + if (c_op == NULL) { + g_free(c_path); + g_set_error(p_err, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + "enhancer: unsupported export extension"); + return FALSE; + } + + /* Apply the preset first. */ + GeglBuffer *p_buf = enhancer_apply(NULL, p_in, p_preset, p_err); + if (p_buf == NULL) { + g_free(c_path); + return FALSE; + } + + /* ku0: capture the save's real success. Record the output's pre-state so + * a pre-existing file can't masquerade as a successful save. */ + GStatBuf st_before; + gboolean b_existed = (g_stat(c_path, &st_before) == 0); + GeglNode *p_graph = gegl_node_new(); GeglNode *p_src = gegl_node_new_child( p_graph, "operation", "gegl:buffer-source", "buffer", p_buf, NULL); - GeglNode *p_save = gegl_node_new_child(p_graph, "operation", "gegl:jpg-save", - "path", c_path, NULL); + GeglNode *p_save; + if (g_str_equal(c_op, "gegl:jpg-save")) { + p_save = gegl_node_new_child(p_graph, "operation", c_op, "path", c_path, + "quality", 95, NULL); + } else { + p_save = + gegl_node_new_child(p_graph, "operation", c_op, "path", c_path, NULL); + } gegl_node_link(p_src, p_save); gegl_node_process(p_save); - gboolean b_ok = g_file_test(c_path, G_FILE_TEST_EXISTS); - g_object_unref(p_graph); g_object_unref(p_buf); + + /* Verify the save actually produced a non-empty file newer than before. */ + GStatBuf st_after; + gboolean b_ok = FALSE; + if (g_stat(c_path, &st_after) == 0 && st_after.st_size > 0) { + if (!b_existed || st_after.st_mtime != st_before.st_mtime || + st_after.st_size != st_before.st_size) { + b_ok = TRUE; + } + } g_free(c_path); - return b_ok; -}
\ No newline at end of file + if (!b_ok) { + g_set_error(p_err, G_IO_ERROR, G_IO_ERROR_FAILED, + "enhancer: export produced no valid file"); + return FALSE; + } + return TRUE; +} + +#if GGAZE_HAVE_GEGL + +GeglBuffer * +enhancer_load(GFile *p_file, GError **p_err) { + g_return_val_if_fail(p_file != NULL, NULL); + char *c_path = g_file_get_path(p_file); + if (c_path == NULL) { + g_set_error(p_err, G_IO_ERROR, G_IO_ERROR_FAILED, + "enhancer: non-local load path"); + return NULL; + } + GeglBuffer *p_buf = NULL; + GeglNode *p_graph = gegl_node_new(); + GeglNode *p_load = gegl_node_new_child(p_graph, "operation", "gegl:load", + "path", c_path, NULL); + GeglNode *p_sink = gegl_node_new_child( + p_graph, "operation", "gegl:buffer-sink", "buffer", &p_buf, NULL); + gegl_node_link(p_load, p_sink); + gegl_node_process(p_sink); + g_object_unref(p_graph); + if (p_buf == NULL) { + g_set_error(p_err, G_IO_ERROR, G_IO_ERROR_FAILED, + "enhancer: failed to load %s", c_path); + g_free(c_path); + return NULL; + } + g_free(c_path); + return p_buf; +} + +GdkTexture * +enhancer_buffer_to_texture(GeglBuffer *p_buf, GError **p_err) { + g_return_val_if_fail(p_buf != NULL, NULL); + gint i_w = gegl_buffer_get_width(p_buf); + gint i_h = gegl_buffer_get_height(p_buf); + if (i_w <= 0 || i_h <= 0) { + g_set_error(p_err, G_IO_ERROR, G_IO_ERROR_FAILED, + "enhancer: empty buffer"); + return NULL; + } + const Babl *p_fmt = babl_format("R'G'B'A u8"); + gint i_stride = i_w * 4; + gsize u_size = (gsize)i_stride * (gsize)i_h; + gpointer p_data = g_malloc(u_size); + GeglRectangle rect = {0, 0, i_w, i_h}; + gegl_buffer_get(p_buf, &rect, 1.0, p_fmt, p_data, i_stride, GEGL_ABYSS_NONE); + GBytes *p_bytes = g_bytes_new_take(p_data, u_size); + GdkTexture *p_tex = gdk_memory_texture_new( + i_w, i_h, GDK_MEMORY_R8G8B8A8_PREMULTIPLIED, p_bytes, i_stride); + g_bytes_unref(p_bytes); + return p_tex; +} + +#endif /* GGAZE_HAVE_GEGL */
\ No newline at end of file diff --git a/src/enhancer.h b/src/enhancer.h index fce3fe7..f466d21 100644 --- a/src/enhancer.h +++ b/src/enhancer.h @@ -1,6 +1,9 @@ #ifndef GGAZE_ENHANCER_H #define GGAZE_ENHANCER_H +#include "ggaze-config.h" + +#include <gdk/gdk.h> #include <gio/gio.h> #include <glib.h> #include <gegl.h> @@ -25,12 +28,25 @@ const GPtrArray *enhancer_get_presets(Enhancer *p_e); GeglBuffer *enhancer_apply(Enhancer *p_e, GeglBuffer *p_in, const EnhancerPreset *p_preset, GError **p_err); -/* Export the enhanced buffer to a file (JPEG quality 95, EXIF Orientation=1). - * Returns TRUE on success. */ +/* Export the enhanced buffer to a file. The saver is chosen from p_out's + * extension: .jpg/.jpeg -> gegl:jpg-save (quality 95), .png -> gegl:png-save, + * .webp -> gegl:webp-save (if available). Other extensions fail with + * G_IO_ERROR_NOT_SUPPORTED. Success is verified by a real stat of the output + * (not pre-existence). Returns TRUE on success. */ gboolean enhancer_export(Enhancer *p_e, GeglBuffer *p_in, const EnhancerPreset *p_preset, GFile *p_out, GError **p_err); +#if GGAZE_HAVE_GEGL +/* Load a file into a GeglBuffer via the gegl:load op. Returns a new buffer + * (caller unrefs) or NULL with p_err set. */ +GeglBuffer *enhancer_load(GFile *p_file, GError **p_err); + +/* Convert a GeglBuffer to a GdkTexture for preview (RGBA8 bytes). Returns a + * new GdkTexture (caller unrefs) or NULL with p_err set. Needs no display. */ +GdkTexture *enhancer_buffer_to_texture(GeglBuffer *p_buf, GError **p_err); +#endif /* GGAZE_HAVE_GEGL */ + G_END_DECLS #endif
\ No newline at end of file diff --git a/src/shortcuts.c b/src/shortcuts.c index 8ea978d..60c0c22 100644 --- a/src/shortcuts.c +++ b/src/shortcuts.c @@ -35,6 +35,8 @@ static const ShortcutEntry SHORTCUTS[] = { {GDK_KEY_t, 0, "win.toggle-view"}, {GDK_KEY_v, 0, "win.mark"}, {GDK_KEY_a, GDK_CONTROL_MASK, "win.mark-all"}, + {GDK_KEY_a, 0, "win.enhance"}, + {GDK_KEY_s, 0, "win.enhance-save"}, {GDK_KEY_question, 0, "win.shortcuts"}, {GDK_KEY_plus, 0, "win.zoom-in"}, {GDK_KEY_equal, 0, "win.zoom-in"}, diff --git a/src/window.c b/src/window.c index 2810d0e..4e4e253 100644 --- a/src/window.c +++ b/src/window.c @@ -15,8 +15,11 @@ #include <adwaita.h> #include <glib.h> +#include <glib/gstdio.h> +#include <string.h> #include <gtk/gtk.h> +#include "ggaze-config.h" #include "gridview.h" #include "info.h" #include "loader/loader.h" @@ -26,6 +29,9 @@ #include "thumbnail.h" #include "trash.h" #include "viewer.h" +#if GGAZE_HAVE_GEGL +#include "enhancer.h" +#endif struct _GgazeWindow { GtkApplicationWindow parent_instance; @@ -45,6 +51,10 @@ struct _GgazeWindow { guint u_slideshow; /* slideshow timeout id (0=off) */ gboolean b_fullscreen; guint u_hdr_hide; /* fullscreen header auto-hide timeout */ +#if GGAZE_HAVE_GEGL + int i_enhance_idx; /* active preset index (-1 = original) */ + Enhancer *p_enhancer; /* GEGL preset engine (NULL w/o GEGL) */ +#endif }; G_DEFINE_TYPE(GgazeWindow, ggaze_window, GTK_TYPE_APPLICATION_WINDOW) @@ -52,6 +62,7 @@ G_DEFINE_TYPE(GgazeWindow, ggaze_window, GTK_TYPE_APPLICATION_WINDOW) /* --- forward decls ------------------------------------------------------- */ static void _load_current(GgazeWindow *p_win); static void _prefetch(GgazeWindow *p_win); +static void _show_texture(GgazeWindow *p_win, GdkTexture *p_tex); static void _update_header(GgazeWindow *p_win); static void _on_grid_activate(GgazeGrid *p_grid, gpointer p_data); static void _show_info(GgazeWindow *p_win); @@ -440,6 +451,24 @@ static const char *SHORTCUTS_UI = " </child>\n" " <child>\n" " <object class=\"GtkShortcutsGroup\">\n" + " <property name=\"title\">Enhance</property>\n" + " <child>\n" + " <object class=\"GtkShortcutsShortcut\">\n" + " <property name=\"accelerator\">a</property>\n" + " <property name=\"title\">Cycle enhance preset " + "(preview)</property>\n" + " </object>\n" + " </child>\n" + " <child>\n" + " <object class=\"GtkShortcutsShortcut\">\n" + " <property name=\"accelerator\">s</property>\n" + " <property name=\"title\">Save enhanced copy</property>\n" + " </object>\n" + " </child>\n" + " </object>\n" + " </child>\n" + " <child>\n" + " <object class=\"GtkShortcutsGroup\">\n" " <property name=\"title\">Zoom</property>\n" " <child>\n" " <object class=\"GtkShortcutsShortcut\">\n" @@ -580,6 +609,156 @@ _action_back(GSimpleAction *p_a, GVariant *p_v, gpointer p_data) { } } +#if GGAZE_HAVE_GEGL +/* win.enhance (key 'a'): cycle the active enhance preset and preview it on + * the current image. Synchronous - may briefly block the UI on large images + * (acceptable for v1; async apply is a later milestone). On any failure reset + * to the original. */ +static void +_action_enhance(GSimpleAction *p_a, GVariant *p_v, gpointer p_data) { + (void)p_a; + (void)p_v; + GgazeWindow *p_win = GGAZE_WINDOW(p_data); + if (p_win->p_nav == NULL || p_win->p_enhancer == NULL) { + return; + } + /* Only meaningful in large view; switch there first so the result shows. */ + const char *c_cur = + gtk_stack_get_visible_child_name(GTK_STACK(p_win->p_stack)); + if (g_strcmp0(c_cur, "large") != 0) { + gtk_stack_set_visible_child_name(GTK_STACK(p_win->p_stack), "large"); + } + + const GPtrArray *p_presets = enhancer_get_presets(p_win->p_enhancer); + gint i_n = (gint)(p_presets != NULL ? p_presets->len : 0); + gint i_next = p_win->i_enhance_idx + 1; + if (i_next >= i_n) { + i_next = -1; /* wrap back to original */ + } + p_win->i_enhance_idx = i_next; + + if (i_next < 0) { + _load_current(p_win); /* restore original (texturecache makes it fast) */ + _update_header(p_win); + return; + } + + GFile *p_file = navigator_get_current(p_win->p_nav); + if (p_file == NULL) { + p_win->i_enhance_idx = -1; + _update_header(p_win); + return; + } + + GError *p_err = NULL; + GeglBuffer *p_buf = enhancer_load(p_file, &p_err); + GdkTexture *p_tex = NULL; + if (p_buf != NULL) { + const EnhancerPreset *p_preset = + g_ptr_array_index((GPtrArray *)p_presets, (guint)i_next); + GeglBuffer *p_enh = + enhancer_apply(p_win->p_enhancer, p_buf, p_preset, &p_err); + if (p_enh != NULL) { + p_tex = enhancer_buffer_to_texture(p_enh, &p_err); + g_object_unref(p_enh); + } + g_object_unref(p_buf); + } + if (p_tex == NULL) { + g_warning("ggaze: enhance failed: %s", + p_err != NULL ? p_err->message : "(no detail)"); + g_clear_error(&p_err); + p_win->i_enhance_idx = -1; + _load_current(p_win); + } else { + _show_texture(p_win, p_tex); + g_object_unref(p_tex); + } + _update_header(p_win); +} + +/* win.enhance-save (key 's'): export the current image with the active preset + * to <stem>-enhanced.<ext>. Never overwrites the original. No-op (with a + * warning) when no preview is active. */ +static void +_action_enhance_save(GSimpleAction *p_a, GVariant *p_v, gpointer p_data) { + (void)p_a; + (void)p_v; + GgazeWindow *p_win = GGAZE_WINDOW(p_data); + if (p_win->p_nav == NULL || p_win->p_enhancer == NULL) { + return; + } + if (p_win->i_enhance_idx < 0) { + g_warning("ggaze: nothing to save (no enhance preview active)"); + return; + } + GFile *p_file = navigator_get_current(p_win->p_nav); + if (p_file == NULL) { + return; + } + + /* Build <stem>-enhanced.<ext>; default .jpg for unknown extensions. */ + char *c_base = g_file_get_basename(p_file); + char *c_dot = strrchr(c_base, '.'); + const char *c_ext = ".jpg"; + if (c_dot != NULL && (g_ascii_strcasecmp(c_dot, ".jpg") == 0 || + g_ascii_strcasecmp(c_dot, ".jpeg") == 0 || + g_ascii_strcasecmp(c_dot, ".png") == 0 || + g_ascii_strcasecmp(c_dot, ".webp") == 0)) { + c_ext = c_dot; + } + char *c_stem; + if (c_dot != NULL && c_ext == c_dot) { + c_stem = g_strndup(c_base, (gsize)(c_dot - c_base)); + } else { + c_stem = g_strdup(c_base); + } + GFile *p_dir = g_file_get_parent(p_file); + char *c_outname = g_strdup_printf("%s-enhanced%s", c_stem, c_ext); + GFile *p_out = g_file_get_child(p_dir, c_outname); + g_free(c_outname); + g_free(c_stem); + g_free(c_base); + g_object_unref(p_dir); + + GError *p_err = NULL; + GeglBuffer *p_buf = enhancer_load(p_file, &p_err); + gboolean b_ok = FALSE; + if (p_buf != NULL) { + const GPtrArray *p_presets = enhancer_get_presets(p_win->p_enhancer); + const EnhancerPreset *p_preset = + g_ptr_array_index((GPtrArray *)p_presets, (guint)p_win->i_enhance_idx); + b_ok = enhancer_export(p_win->p_enhancer, p_buf, p_preset, p_out, &p_err); + g_object_unref(p_buf); + } + char *c_saved = b_ok ? g_file_get_basename(p_out) : NULL; + g_object_unref(p_out); + if (b_ok) { + g_printerr("ggaze: saved %s\n", c_saved); + } else { + g_warning("ggaze: enhance-save failed: %s", + p_err != NULL ? p_err->message : "(no detail)"); + } + g_free(c_saved); + g_clear_error(&p_err); +} +#else /* !GGAZE_HAVE_GEGL */ +static void +_action_enhance(GSimpleAction *p_a, GVariant *p_v, gpointer p_data) { + (void)p_a; + (void)p_v; + (void)p_data; + g_warning("ggaze: GEGL not built in"); +} +static void +_action_enhance_save(GSimpleAction *p_a, GVariant *p_v, gpointer p_data) { + (void)p_a; + (void)p_v; + (void)p_data; + g_warning("ggaze: GEGL not built in"); +} +#endif /* GGAZE_HAVE_GEGL */ + static gboolean _slideshow_tick(gpointer p_data) { GgazeWindow *p_win = GGAZE_WINDOW(p_data); @@ -646,6 +825,8 @@ static const GActionEntry ACTIONS[] = { {.name = "slideshow", .activate = _action_slideshow}, {.name = "info", .activate = _action_info}, {.name = "back", .activate = _action_back}, + {.name = "enhance", .activate = _action_enhance}, + {.name = "enhance-save", .activate = _action_enhance_save}, }; /* --- drop target --------------------------------------------------------- */ @@ -675,7 +856,13 @@ drop_cb(GtkDropTarget *p_t, const GValue *p_val, gdouble d_x, gdouble d_y, static void nav_changed_cb(Navigator *p_nav, gpointer p_data) { (void)p_nav; - _load_current(GGAZE_WINDOW(p_data)); + GgazeWindow *p_win = GGAZE_WINDOW(p_data); +#if GGAZE_HAVE_GEGL + /* New image starts fresh: drop any enhanced preview so we don't show a + * stale enhanced texture for a different file. */ + p_win->i_enhance_idx = -1; +#endif + _load_current(p_win); } static void @@ -866,6 +1053,23 @@ _update_header(GgazeWindow *p_win) { c_title = c_tmp; } } +#if GGAZE_HAVE_GEGL + /* Append the active enhance preset name when a preview is showing. */ + if (p_win->i_enhance_idx >= 0 && p_win->p_enhancer != NULL && + c_title != NULL) { + const GPtrArray *p_presets = enhancer_get_presets(p_win->p_enhancer); + if (p_presets != NULL && (guint)p_win->i_enhance_idx < p_presets->len) { + const EnhancerPreset *p_preset = g_ptr_array_index( + (GPtrArray *)p_presets, (guint)p_win->i_enhance_idx); + if (p_preset->c_name != NULL) { + char *c_tmp = + g_strdup_printf("%s \u00b7 %s", c_title, p_preset->c_name); + g_free(c_title); + c_title = c_tmp; + } + } + } +#endif if (c_title == NULL) { c_title = g_strdup("ggaze"); } @@ -904,6 +1108,9 @@ ggaze_window_dispose(GObject *p_obj) { g_clear_pointer(&p_win->p_cache, texturecache_delete); g_clear_pointer(&p_win->p_trash, trash_delete); g_clear_pointer(&p_win->p_thumb, thumbnail_delete); +#if GGAZE_HAVE_GEGL + g_clear_pointer(&p_win->p_enhancer, enhancer_delete); +#endif /* p_stack/p_viewer/p_grid are GtkWidgets parented to the window; GTK * releases them. */ G_OBJECT_CLASS(ggaze_window_parent_class)->dispose(p_obj); @@ -951,6 +1158,10 @@ ggaze_window_init(GgazeWindow *p_win) { p_win->p_trash = NULL; /* created on open */ p_win->p_grid = NULL; /* created on open */ p_win->i_grid_size = 128; +#if GGAZE_HAVE_GEGL + p_win->i_enhance_idx = -1; /* start on the original */ + p_win->p_enhancer = enhancer_new(); +#endif /* Header bar (libadwaita, decision #29). */ GtkWidget *p_header = adw_header_bar_new(); |
