diff options
| author | Paul Buetow <paul@buetow.org> | 2026-07-12 17:30:26 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-07-12 17:30:26 +0300 |
| commit | 547b810eea19b0c72731e30ca3e2e339593c1833 (patch) | |
| tree | e19fc9308d2f93ffd1d558ec705effc6d9ffb752 /src | |
| parent | 1770b12d5d95476310c27ccf39a91b5ba4a3f119 (diff) | |
skeleton: GtkApplication, window, GSettings schema, desktop, tests (ft0)
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.
Diffstat (limited to 'src')
| -rw-r--r-- | src/app.c | 74 | ||||
| -rw-r--r-- | src/app.h | 28 | ||||
| -rw-r--r-- | src/main.c | 51 | ||||
| -rw-r--r-- | src/meson.build | 13 | ||||
| -rw-r--r-- | src/window.c | 74 | ||||
| -rw-r--r-- | src/window.h | 37 |
6 files changed, 274 insertions, 3 deletions
diff --git a/src/app.c b/src/app.c new file mode 100644 index 0000000..587feb7 --- /dev/null +++ b/src/app.c @@ -0,0 +1,74 @@ +/*:* + * ggaze — application object + * + * Implements GgazeApp (GgazeApp : AdwApplication : GtkApplication). Wires the + * GApplication `open` signal (file or folder) and the default `activate` + * (no-arg) path to a GgazeWindow, reusing the active window for the + * single-instance "replace on new open" behaviour (decision #32). Real + * file-vs-folder resolution is the navigator's job in M2; M0 just remembers + * the path and titles the window. See docs/architecture.md. + * + * Copyright (c) 2026 ggaze contributors + * SPDX-License-Identifier: GPL-3.0-or-later + *:*/ + +#include "app.h" +#include "window.h" +#include "ggaze-config.h" + +#include <glib.h> + +struct _GgazeApp { + AdwApplication parent_instance; +}; + +G_DEFINE_TYPE(GgazeApp, ggaze_app, ADW_TYPE_APPLICATION) + +static GgazeWindow * +_ensure_window(GgazeApp *p_app) { + GtkWindow *p_active = + gtk_application_get_active_window(GTK_APPLICATION(p_app)); + if (p_active != NULL && GGAZE_IS_WINDOW(p_active)) { + return (GgazeWindow *)p_active; + } + return (ggaze_window_new(p_app)); +} + +static void +ggaze_app_activate(GApplication *p_app) { + GgazeWindow *p_win = _ensure_window(GGAZE_APP(p_app)); + gtk_window_present(GTK_WINDOW(p_win)); +} + +static void +ggaze_app_open(GApplication *p_app, GFile **p_files, gint n_files, + const gchar *c_hint) { + (void)c_hint; + GgazeApp *p_app_self = GGAZE_APP(p_app); + GgazeWindow *p_win = _ensure_window(p_app_self); + /* open-question Z / decision #27: one file opens it; many files open the + * first file's folder with the first current (navigator wiring in M2). */ + if (n_files > 0 && p_files != NULL) { + ggaze_window_open(p_win, p_files[0]); + } + gtk_window_present(GTK_WINDOW(p_win)); +} + +static void +ggaze_app_init(GgazeApp *p_app) { + (void)p_app; +} + +static void +ggaze_app_class_init(GgazeAppClass *p_klass) { + GApplicationClass *p_app_class = G_APPLICATION_CLASS(p_klass); + p_app_class->activate = ggaze_app_activate; + p_app_class->open = ggaze_app_open; +} + +GgazeApp * +ggaze_app_new(void) { + return ( + GGAZE_APP(g_object_new(GGAZE_TYPE_APP, "application-id", GGAZE_APP_ID, + "flags", G_APPLICATION_HANDLES_OPEN, NULL))); +}
\ No newline at end of file diff --git a/src/app.h b/src/app.h new file mode 100644 index 0000000..7b40ff6 --- /dev/null +++ b/src/app.h @@ -0,0 +1,28 @@ +#ifndef GGAZE_APP_H +#define GGAZE_APP_H + +/*:* + * ggaze — application object + * + * GgazeApp is the GApplication/single-instance entry point. It owns the + * GtkApplication, registers actions, and routes the GApplication `open` + * signal (a file OR a directory) to the window. See docs/architecture.md + * "Responsibilities / app". + * + * Copyright (c) 2026 ggaze contributors + * SPDX-License-Identifier: GPL-3.0-or-later + *:*/ + +#include <adwaita.h> + +G_BEGIN_DECLS + +#define GGAZE_TYPE_APP (ggaze_app_get_type()) +G_DECLARE_FINAL_TYPE(GgazeApp, ggaze_app, GGAZE, APP, AdwApplication) + +/* Construct a new single-instance ggaze application. */ +GgazeApp *ggaze_app_new(void); + +G_END_DECLS + +#endif /* GGAZE_APP_H */
\ No newline at end of file diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..69cf84d --- /dev/null +++ b/src/main.c @@ -0,0 +1,51 @@ +/*:* + * ggaze — entry point + * + * Builds the GgazeApp, registers the `--version` option, and hands control to + * g_application_run. `--help` is provided by GApplication itself; `--version` + * is handled in handle-local-options and exits before any window is created + * (so it works headless, which the unit test relies on). See docs/roadmap.md + * M0. + * + * Copyright (c) 2026 ggaze contributors + * SPDX-License-Identifier: GPL-3.0-or-later + *:*/ + +#include "app.h" +#include "ggaze-config.h" + +#include <glib.h> +#include <gtk/gtk.h> + +static const GOptionEntry GGAZE_OPTIONS[] = { + {"version", '\0', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, NULL, + "Show version information and exit", NULL}, + {NULL, '\0', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, NULL, NULL, NULL}, +}; + +static gint +on_local_options(GApplication *p_app, GVariantDict *p_opts, gpointer p_data) { + (void)p_app; + (void)p_data; + gboolean b_version = FALSE; + if (g_variant_dict_lookup(p_opts, "version", "b", &b_version) && b_version) { + g_print("ggaze %s\n", GGAZE_VERSION); + return (0); /* exit success, before activate */ + } + return (-1); /* continue normal startup */ +} + +int +main(int i_argc, char **c_argv) { + g_set_application_name("ggaze"); + + GgazeApp *p_app = ggaze_app_new(); + g_application_add_main_option_entries(G_APPLICATION(p_app), GGAZE_OPTIONS); + g_signal_connect(p_app, "handle-local-options", G_CALLBACK(on_local_options), + NULL); + + int i_status = g_application_run(G_APPLICATION(p_app), i_argc, c_argv); + + g_object_unref(p_app); + return (i_status); +}
\ No newline at end of file diff --git a/src/meson.build b/src/meson.build index a46993e..187c79d 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,3 +1,10 @@ -# ggaze production sources. Populated from M0 onward (see docs/roadmap.md and -# docs/architecture.md for the module map). Phase 0 ships no sources yet — -# only the build/test/CI/convention harness, so `ninja` has no C targets here.
\ No newline at end of file +# ggaze executable. The app/window code lives in libggae (see meson.build); the +# executable only adds the entry point and the --version CLI option. + +ggaze_exe = executable('ggaze', + 'main.c', + include_directories : [inc, src_inc], + dependencies : ggaze_deps, + link_with : ggaze_lib, + install : true, +)
\ No newline at end of file diff --git a/src/window.c b/src/window.c new file mode 100644 index 0000000..049be4e --- /dev/null +++ b/src/window.c @@ -0,0 +1,74 @@ +/*:* + * ggaze — main window + * + * Implements GgazeWindow. Builds an AdwHeaderBar + a GtkStack with two named + * placeholder children ("grid", "large"); the real views arrive in M1 (large) + * and M7 (grid). Tracks the current GFile for later milestones. + * + * Copyright (c) 2026 ggaze contributors + * SPDX-License-Identifier: GPL-3.0-or-later + *:*/ + +#include "window.h" + +#include <adwaita.h> +#include <glib.h> + +struct _GgazeWindow { + GtkApplicationWindow parent_instance; + GFile *p_file; /* current file/folder, remembered for later use */ + GtkWidget *p_stack; /* GtkStack: grid/large placeholder children */ +}; + +G_DEFINE_TYPE(GgazeWindow, ggaze_window, GTK_TYPE_APPLICATION_WINDOW) + +static void +ggaze_window_dispose(GObject *p_obj) { + GgazeWindow *p_win = GGAZE_WINDOW(p_obj); + g_clear_object(&p_win->p_file); + G_OBJECT_CLASS(ggaze_window_parent_class)->dispose(p_obj); +} + +static void +ggaze_window_class_init(GgazeWindowClass *p_klass) { + GObjectClass *p_obj_class = G_OBJECT_CLASS(p_klass); + p_obj_class->dispose = ggaze_window_dispose; +} + +static void +ggaze_window_init(GgazeWindow *p_win) { + /* Header bar (libadwaita, decision #29). */ + GtkWidget *p_header = adw_header_bar_new(); + gtk_window_set_titlebar(GTK_WINDOW(p_win), p_header); + + /* Two-view stack. Children are placeholders for M1 (large) and M7 (grid). */ + p_win->p_stack = gtk_stack_new(); + gtk_stack_set_transition_type(GTK_STACK(p_win->p_stack), + GTK_STACK_TRANSITION_TYPE_CROSSFADE); + gtk_window_set_child(GTK_WINDOW(p_win), p_win->p_stack); + + GtkWidget *p_grid = gtk_label_new("grid"); + gtk_widget_add_css_class(p_grid, "dim-label"); + GtkWidget *p_large = gtk_label_new("large"); + gtk_widget_add_css_class(p_large, "dim-label"); + gtk_stack_add_named(GTK_STACK(p_win->p_stack), p_grid, "grid"); + gtk_stack_add_named(GTK_STACK(p_win->p_stack), p_large, "large"); + gtk_stack_set_visible_child_name(GTK_STACK(p_win->p_stack), "grid"); +} + +GgazeWindow * +ggaze_window_new(GgazeApp *p_app) { + return (GGAZE_WINDOW(g_object_new(GGAZE_TYPE_WINDOW, "application", p_app, + "default-width", 800, "default-height", + 600, NULL))); +} + +void +ggaze_window_open(GgazeWindow *p_win, GFile *p_file) { + g_return_if_fail(GGAZE_IS_WINDOW(p_win)); + g_return_if_fail(G_IS_FILE(p_file)); + g_set_object(&p_win->p_file, p_file); + char *c_name = g_file_get_basename(p_file); + gtk_window_set_title(GTK_WINDOW(p_win), c_name); + g_free(c_name); +}
\ No newline at end of file diff --git a/src/window.h b/src/window.h new file mode 100644 index 0000000..aeddf64 --- /dev/null +++ b/src/window.h @@ -0,0 +1,37 @@ +#ifndef GGAZE_WINDOW_H +#define GGAZE_WINDOW_H + +/*:* + * ggaze — main window + * + * GgazeWindow : GtkApplicationWindow owns the layout: an AdwHeaderBar and a + * GtkStack with two placeholder children (`grid`, `large`). M0 keeps the + * stack empty of real views; gridview (M7) and viewer (M1) populate it. The + * window remembers the current GFile so later milestones can build on it. + * See docs/architecture.md "Responsibilities / window". + * + * Copyright (c) 2026 ggaze contributors + * SPDX-License-Identifier: GPL-3.0-or-later + *:*/ + +#include "app.h" + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define GGAZE_TYPE_WINDOW (ggaze_window_get_type()) +G_DECLARE_FINAL_TYPE(GgazeWindow, ggaze_window, GGAZE, WINDOW, + GtkApplicationWindow) + +/* Construct a new window attached to p_app. */ +GgazeWindow *ggaze_window_new(GgazeApp *p_app); + +/* Remember p_file as the current file (or folder) and title the window with + * its basename. The GFile reference is held until replaced or the window is + * destroyed. Real file-vs-folder handling lands in M2. */ +void ggaze_window_open(GgazeWindow *p_win, GFile *p_file); + +G_END_DECLS + +#endif /* GGAZE_WINDOW_H */
\ No newline at end of file |
