summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-07-12 17:30:26 +0300
committerPaul Buetow <paul@buetow.org>2026-07-12 17:30:26 +0300
commit547b810eea19b0c72731e30ca3e2e339593c1833 (patch)
treee19fc9308d2f93ffd1d558ec705effc6d9ffb752 /src
parent1770b12d5d95476310c27ccf39a91b5ba4a3f119 (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.c74
-rw-r--r--src/app.h28
-rw-r--r--src/main.c51
-rw-r--r--src/meson.build13
-rw-r--r--src/window.c74
-rw-r--r--src/window.h37
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