diff --git a/.gitignore b/.gitignore index 99e75c7..d6b4f67 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,10 @@ # ---> C # Prerequisites -*.d +#.d # binary sprechen +maturita error.txt error.log CLAUDE.md diff --git a/Makefile b/Makefile index b3f4748..35a0947 100644 --- a/Makefile +++ b/Makefile @@ -16,8 +16,8 @@ GTK_LIBS := $(shell $(PKG_CONFIG) --libs gtk4) CFLAGS = -Wall -Wextra -Wno-deprecated-declarations $(GTK_CFLAGS) LIBS = $(GTK_LIBS) -lm -TARGET = sprechen -SRC = sprechen.c +TARGET = maturita +SRC = maturita.c all: $(TARGET) diff --git a/README.md b/README.md index 1af75fa..fa1abd1 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,24 @@ -# Sprechen.c +# maturita.c -A German learning application written in C using GTK 4. +An educational application written in C using GTK 4. ## Description -Sprechen.c is an educational program designed for practicing German language -skills, aimed at high school and gymnasium students. The UI is available in -Czech and English and is built as a single C file (`sprechen.c`) that renders a -modern, CSS-styled GTK 4 interface. +maturita.c is an educational program for high-school and gymnasium students +preparing for their maturita exam. The UI is available in Czech and English and +is built as a single C file (`maturita.c`) that renders a modern, CSS-styled +GTK 4 interface. ## Features - **Welcome screen** – branded greeting with a description of the program and a continue button to get started +- **Subjects ("Předměty")** – adaptive serpentine subject map with 13 subjects + - only **Deutsch** is open (round bubble with the German flag); picking it + opens the German learning path + - the other subjects are dimmed/locked placeholders until their content is + added + - layout folds into multiple rows when the window is narrow - **Learning roadmap** – adaptive serpentine learning path with 10 units - each unit numbered and labeled with its topic (e.g. "Neue Freunde") - units 1 and 2 are open; completed units shown in green with a checkmark @@ -115,7 +121,7 @@ make Or manually: ```bash -gcc -o sprechen sprechen.c `pkg-config --cflags --libs gtk4` -lm +gcc -o maturita maturita.c `pkg-config --cflags --libs gtk4` -lm ``` ### Running the Application @@ -127,22 +133,24 @@ make run or ```bash -./sprechen +./maturita ``` ## Usage When launched, the application shows the welcome screen. From there: -1. Click **"Pokračuj" / "Continue"** to open the learning roadmap. -2. Browse the units along the path; the roadmap reflows into rows and scrolls +1. Click **"Pokračuj" / "Continue"** to open the **Předměty / Subjects** map. +2. Click the **Deutsch** bubble (German flag) to open the German learning + roadmap. The other subjects are locked for now. +3. Browse the units along the path; the roadmap reflows into rows and scrolls if the window is too narrow. -3. Click the **"Neue Freunde"** node to open unit 1 or **"Aus aller Welt"** to +4. Click the **"Neue Freunde"** node to open unit 1 or **"Aus aller Welt"** to open unit 2, then pick any bubble to start an exercise. Unit 2 is available even if unit 1 is not finished yet. -4. Use the **back button** in the top-left corner to return to the previous +5. Use the **back button** in the top-left corner to return to the previous screen. -5. Open **Nastavení / Settings** (gear icon, top-right) to switch dark/light +6. Open **Nastavení / Settings** (gear icon, top-right) to switch dark/light mode, pick a color theme, or change the interface language. Units 1 and 2 are currently implemented. Locked units do nothing – they are @@ -160,7 +168,7 @@ to the app on first finish). ## Project structure ``` -sprechen.c entire application (UI, navigation, themes, exercises) +maturita.c entire application (UI, navigation, themes, exercises) Makefile build & run targets README.md this file LICENSE MIT license @@ -174,18 +182,18 @@ progress/ created at runtime The app ships with ten palettes – Catppuccin, Nord, Dracula, Rose Pine, Ocean, Gruvbox, Solarized, Everforest, Monokai and One Dark – each available in dark -and light mode. Colors are defined as theme palettes in `sprechen.c` and +and light mode. Colors are defined as theme palettes in `maturita.c` and applied through a generated CSS stylesheet (plus matching Cairo colors for the roadmap rails and icons). Change the look from the in-app settings panel, or edit the palette tables / -`build_theme_css()` in `sprechen.c` and rebuild. +`build_theme_css()` in `maturita.c` and rebuild. ## Languages The interface can be switched between Czech and English from the settings panel; the choice takes effect immediately and is remembered between runs. -Translations live in two tables in `sprechen.c`: `tr_ui` holds interface +Translations live in two tables in `maturita.c`: `tr_ui` holds interface strings under short keys, while `tr_content` translates the Czech meanings shown next to the German exercise items and uses the Czech text itself as the key. Widgets are registered with `i18n_bind()` so `apply_language()` can @@ -196,7 +204,7 @@ German in both languages. ## To-Do Plans -Future enhancements planned for Sprechen.c: +Future enhancements planned for maturita.c: - [ ] Wire unit nodes to actual German vocabulary/grammar exercises - [x] Unit 1 ("Neue Freunde") – 13 interactive exercises with progress saving diff --git a/sprechen.c b/maturita.c similarity index 94% rename from sprechen.c rename to maturita.c index a49896e..b9dd38f 100644 --- a/sprechen.c +++ b/maturita.c @@ -358,11 +358,11 @@ static const TrEntry tr_ui[] = { {"theme", "TÉMA", "THEME"}, {"language", "JAZYK", "LANGUAGE"}, {"welcome_body", - "Sprechen.C je vzdělávací program na procvičování Němčiny, určený " - "pro studenty středních škol a gymnázií.\n\n" + "maturita.C je vzdělávací program pro studenty středních škol a " + "gymnázií na přípravu k maturitě.\n\n" "Program je napsaný v Céčku studentama ze SSŠVT!", - "Sprechen.C is an educational app for practicing German, made for " - "high-school and gymnasium students.\n\n" + "maturita.C is an educational app for high-school and gymnasium " + "students to prepare for their maturita exam.\n\n" "The program is written in C by students from SSŠVT!"}, {"continue", "Pokračuj", "Continue"}, {"roadmap_title", "Učební plán", "Learning path"}, @@ -458,6 +458,25 @@ static const TrEntry tr_ui[] = { {"hm_done", "Výborně! Uhodli jste všechna povolání.", "Great! You guessed all the jobs."}, {"hm_guess", "Hádejte písmeno", "Guess a letter"}, + {"subjects_title", "Předměty", "Subjects"}, + {"subjects_sub", "Zatím je dostupný předmět Deutsch.", + "Only Deutsch is available so far."}, + {"Český jazyk a literatura", "Český jazyk a literatura", + "Czech Language and Literature"}, + {"Občanská nauka", "Občanská nauka", "Civics"}, + {"English", "English", "English"}, + {"Deutsch", "Deutsch", "Deutsch"}, + {"Matematika", "Matematika", "Mathematics"}, + {"Fyzika", "Fyzika", "Physics"}, + {"Základy Přírodopisných věd", "Základy Přírodopisných věd", + "Fundamentals of Natural Sciences"}, + {"Technická grafika", "Technická grafika", "Technical Drawing"}, + {"Prezentační grafika", "Prezentační grafika", "Presentation Graphics"}, + {"Správa počítačových sítí", "Správa počítačových sítí", + "Computer Network Administration"}, + {"Programování", "Programování", "Programming"}, + {"Technické vybavení", "Technické vybavení", "Computer Hardware"}, + {"Programové vybavení", "Programové vybavení", "Software"}, {NULL, NULL, NULL} }; @@ -753,11 +772,11 @@ static void refresh_welcome_heading(void) { return; if (app_lang == LANG_EN) markup = g_strdup_printf( - "Welcome to Sprechen.C!", + "Welcome to maturita.C!", app_theme.accent); else markup = g_strdup_printf( - "Vítejte ve Sprechen.C!", + "Vítejte ve maturita.C!", app_theme.accent); gtk_label_set_markup(GTK_LABEL(welcome_heading), markup); g_free(markup); @@ -1829,7 +1848,7 @@ static void on_nav_clicked(GtkButton *button, gpointer user_data) { static void on_continue_clicked(GtkButton *button, gpointer user_data) { (void)button; (void)user_data; - gtk_stack_set_visible_child_name(main_stack, "roadmap"); + gtk_stack_set_visible_child_name(main_stack, "subjects"); } static GtkWidget *make_back_button(const char *target) { @@ -2347,7 +2366,7 @@ static GtkWidget *build_roadmap_page(void) { gtk_widget_set_margin_bottom(page, 24); gtk_box_append(GTK_BOX(page), - top_bar("welcome", "roadmap_title", "roadmap_sub")); + top_bar("subjects", "roadmap_title", "roadmap_sub")); scroll = gtk_scrolled_window_new(); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), @@ -2391,6 +2410,357 @@ static GtkWidget *build_roadmap_page(void) { } +/* ------------------------------------------------------------------ */ +/* Subjects page ("Předměty") - serpentine subject map */ +/* ------------------------------------------------------------------ */ + +#define NUM_SUBJECTS 13 +#define SUB_BUBBLE NODE_SIZE /* round node, matches .unit-node */ +#define SUB_MX 110.0 +#define SUB_MY 150.0 +#define SUB_SPAC 220.0 +#define SUB_GAP 210.0 +#define SUB_WAVE 34.0 + +/* Index 0 is Deutsch and is the only unlocked subject for now. */ +static const char *sub_keys[NUM_SUBJECTS] = { + "Deutsch", + "Český jazyk a literatura", + "Občanská nauka", + "English", + "Matematika", + "Fyzika", + "Základy Přírodopisných věd", + "Technická grafika", + "Prezentační grafika", + "Správa počítačových sítí", + "Programování", + "Technické vybavení", + "Programové vybavení", +}; + +static GtkWidget *sub_scroll; +static GtkWidget *sub_fixed; +static GtkWidget *sub_rail; +static GtkWidget *sub_cells[NUM_SUBJECTS]; +static GtkWidget *sub_labels[NUM_SUBJECTS]; +static double sub_cx[NUM_SUBJECTS]; +static double sub_cy[NUM_SUBJECTS]; +static int sub_rows = 1; +static int sub_cols = NUM_SUBJECTS; +static int sub_cw = (int)(2.0 * SUB_MX + (NUM_SUBJECTS - 1) * SUB_SPAC); +static int sub_ch = (int)(2.0 * SUB_MY); +static guint sub_idle; + +static void sub_layout_geometry(double avail) { + const double phase = 2.0 * G_PI * 1.7 / (double)(NUM_SUBJECTS - 1); + int rows; + int r, c, i; + + for (rows = 1; rows <= NUM_SUBJECTS; rows++) { + int cols = (NUM_SUBJECTS + rows - 1) / rows; + if (2.0 * SUB_MX + (cols - 1) * SUB_SPAC <= avail + 1.0) + break; + } + if (rows > NUM_SUBJECTS) + rows = NUM_SUBJECTS; + sub_rows = rows; + sub_cols = (NUM_SUBJECTS + rows - 1) / rows; + if (sub_cols < 1) + sub_cols = 1; + sub_cw = (int)(2.0 * SUB_MX + (sub_cols - 1) * SUB_SPAC); + sub_ch = (int)(2.0 * SUB_MY + (rows - 1) * SUB_GAP); + + for (r = 0; r < rows; r++) { + int base = r * sub_cols; + int len = MIN(sub_cols, NUM_SUBJECTS - base); + int fwd = (r % 2) == 0; + for (c = 0; c < len; c++) { + i = base + c; + int cc = fwd ? c : (sub_cols - 1 - c); + sub_cx[i] = SUB_MX + cc * SUB_SPAC; + sub_cy[i] = SUB_MY + r * SUB_GAP + + SUB_WAVE * sin((double)i * phase); + } + } +} + +static void sub_point(double t, double *ox, double *oy) { + int k = (int)t; + double u = t - (double)k; + double u2, u3; + double p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y; + + if (k < 0) { k = 0; u = 0.0; } + if (k >= NUM_SUBJECTS - 1) { k = NUM_SUBJECTS - 2; u = 1.0; } + + p1x = sub_cx[k]; p1y = sub_cy[k]; + p2x = sub_cx[k + 1]; p2y = sub_cy[k + 1]; + if (k - 1 >= 0) { p0x = sub_cx[k - 1]; p0y = sub_cy[k - 1]; } + else { p0x = p1x - (p2x - p1x); p0y = p1y - (p2y - p1y); } + if (k + 2 < NUM_SUBJECTS) { p3x = sub_cx[k + 2]; p3y = sub_cy[k + 2]; } + else { p3x = p2x + (p2x - p1x); p3y = p2y + (p2y - p1y); } + + u2 = u * u; + u3 = u2 * u; + *ox = 0.5 * (2.0 * p1x + (-p0x + p2x) * u + + (2.0 * p0x - 5.0 * p1x + 4.0 * p2x - p3x) * u2 + + (-p0x + 3.0 * p1x - 3.0 * p2x + p3x) * u3); + *oy = 0.5 * (2.0 * p1y + (-p0y + p2y) * u + + (2.0 * p0y - 5.0 * p1y + 4.0 * p2y - p3y) * u2 + + (-p0y + 3.0 * p1y - 3.0 * p2y + p3y) * u3); +} + +static void sub_path(cairo_t *cr, double t0, double t1) { + const double steps_per_unit = 48.0; + int n = (int)((t1 - t0) * steps_per_unit); + double x, y; + int s; + + if (n < 1) + n = 1; + sub_point(t0, &x, &y); + cairo_move_to(cr, x, y); + for (s = 1; s <= n; s++) { + sub_point(t0 + (t1 - t0) * (double)s / (double)n, &x, &y); + cairo_line_to(cr, x, y); + } +} + +static void draw_sub_rail(GtkDrawingArea *area, cairo_t *cr, + int width, int height, gpointer user_data) { + const double t_end = (double)(NUM_SUBJECTS - 1); + const Rgb rail = color_from_hex(app_theme.rail); + const Rgb mauve = color_from_hex(app_theme.accent); + double hx, hy; + + (void)area; + (void)width; + (void)height; + (void)user_data; + + cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR); + cairo_paint(cr); + cairo_set_operator(cr, CAIRO_OPERATOR_OVER); + cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND); + cairo_set_line_join(cr, CAIRO_LINE_JOIN_ROUND); + + sub_point(0.0, &hx, &hy); + draw_node_halo(cr, hx, hy, SUB_BUBBLE * 1.05, + mauve.r, mauve.g, mauve.b, 0.18); + + cairo_new_path(cr); + sub_path(cr, 0.0, t_end); + cairo_set_line_width(cr, 14); + cairo_set_source_rgb(cr, rail.r, rail.g, rail.b); + cairo_stroke(cr); +} + +static void sub_apply_layout(void) { + int i; + + if (!sub_fixed) + return; + + for (i = 0; i < NUM_SUBJECTS; i++) { + if (sub_cells[i]) + gtk_fixed_move(GTK_FIXED(sub_fixed), sub_cells[i], + (int)(sub_cx[i] - SUB_BUBBLE / 2.0), + (int)(sub_cy[i] - SUB_BUBBLE / 2.0)); + if (sub_labels[i]) + gtk_fixed_move(GTK_FIXED(sub_fixed), sub_labels[i], + (int)(sub_cx[i] - (SUB_SPAC - 24.0) / 2.0), + (int)(sub_cy[i] + SUB_BUBBLE / 2.0 + 10.0)); + } + + gtk_widget_set_size_request(sub_fixed, sub_cw, sub_ch); + gtk_widget_set_size_request(sub_rail, sub_cw, sub_ch); + gtk_fixed_move(GTK_FIXED(sub_fixed), sub_rail, 0, 0); + gtk_widget_queue_draw(sub_rail); +} + +static void sub_relayout(void) { + GtkAdjustment *hadj; + double avail; + + if (!sub_scroll) + return; + hadj = gtk_scrolled_window_get_hadjustment( + GTK_SCROLLED_WINDOW(sub_scroll)); + avail = gtk_adjustment_get_page_size(hadj); + if (avail < 1.0) + return; + sub_layout_geometry(avail); + sub_apply_layout(); +} + +static gboolean sub_relayout_idle(gpointer data) { + (void)data; + sub_idle = 0; + sub_relayout(); + return G_SOURCE_REMOVE; +} + +static void sub_relayout_later(void) { + if (sub_idle == 0) + sub_idle = g_idle_add(sub_relayout_idle, NULL); +} + +static void sub_adjust_notify(GtkAdjustment *adj, GParamSpec *ps, + gpointer data) { + (void)adj; + (void)ps; + (void)data; + sub_relayout_later(); +} + +/* German flag (black / red / gold) drawn in a clipped circle. */ +static void draw_german_flag(GtkDrawingArea *area, cairo_t *cr, + int width, int height, gpointer data) { + double cx = width / 2.0; + double cy = height / 2.0; + double r = MIN(width, height) / 2.0 - 2.0; + double band = (double)height / 3.0; + + (void)area; + (void)data; + + cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR); + cairo_paint(cr); + cairo_set_operator(cr, CAIRO_OPERATOR_OVER); + + cairo_save(cr); + cairo_new_path(cr); + cairo_arc(cr, cx, cy, r, 0.0, 2.0 * G_PI); + cairo_clip(cr); + + cairo_new_path(cr); + cairo_rectangle(cr, 0.0, 0.0, (double)width, band); + cairo_set_source_rgb(cr, 0.18, 0.18, 0.19); + cairo_fill(cr); + + cairo_new_path(cr); + cairo_rectangle(cr, 0.0, band, (double)width, band); + cairo_set_source_rgb(cr, 0.72, 0.27, 0.24); + cairo_fill(cr); + + cairo_new_path(cr); + cairo_rectangle(cr, 0.0, 2.0 * band, (double)width, band); + cairo_set_source_rgb(cr, 0.84, 0.70, 0.32); + cairo_fill(cr); + + cairo_restore(cr); +} + +static GtkWidget *build_subjects_page(void) { + GtkWidget *page; + GtkWidget *scroll; + GtkWidget *wrap; + GtkWidget *fixed; + GtkWidget *rail; + GtkAdjustment *ha; + GtkAdjustment *va; + int i; + + sub_layout_geometry(1000.0); + + page = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); + gtk_widget_set_hexpand(page, TRUE); + gtk_widget_set_vexpand(page, TRUE); + gtk_widget_set_margin_start(page, 32); + gtk_widget_set_margin_end(page, 32); + gtk_widget_set_margin_top(page, 24); + gtk_widget_set_margin_bottom(page, 24); + + gtk_box_append(GTK_BOX(page), + top_bar("welcome", "subjects_title", "subjects_sub")); + + scroll = gtk_scrolled_window_new(); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_widget_set_vexpand(scroll, TRUE); + gtk_widget_set_margin_top(scroll, 14); + gtk_box_append(GTK_BOX(page), scroll); + sub_scroll = scroll; + + wrap = gtk_center_box_new(); + gtk_widget_set_vexpand(wrap, TRUE); + gtk_widget_set_hexpand(wrap, TRUE); + gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scroll), wrap); + + fixed = gtk_fixed_new(); + gtk_widget_set_halign(fixed, GTK_ALIGN_CENTER); + gtk_widget_set_valign(fixed, GTK_ALIGN_CENTER); + gtk_widget_set_size_request(fixed, sub_cw, sub_ch); + gtk_center_box_set_center_widget(GTK_CENTER_BOX(wrap), fixed); + sub_fixed = fixed; + + rail = gtk_drawing_area_new(); + gtk_widget_set_size_request(rail, sub_cw, sub_ch); + gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(rail), draw_sub_rail, + NULL, NULL); + gtk_fixed_put(GTK_FIXED(fixed), rail, 0, 0); + sub_rail = rail; + + for (i = 0; i < NUM_SUBJECTS; i++) { + GtkWidget *card; + GtkWidget *lbl; + + card = gtk_button_new(); + gtk_widget_add_css_class(card, "unit-node"); + gtk_widget_set_can_focus(card, FALSE); + gtk_widget_set_size_request(card, (int)SUB_BUBBLE, (int)SUB_BUBBLE); + + if (i == 0) { + GtkWidget *flag = gtk_drawing_area_new(); + gtk_widget_add_css_class(card, "current"); + gtk_widget_set_size_request(flag, (int)SUB_BUBBLE, + (int)SUB_BUBBLE); + gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(flag), + draw_german_flag, NULL, NULL); + gtk_button_set_child(GTK_BUTTON(card), flag); + g_object_set_data_full(G_OBJECT(card), "target", + g_strdup("roadmap"), g_free); + g_signal_connect(card, "clicked", G_CALLBACK(on_nav_clicked), NULL); + } else { + GtkWidget *lock; + gtk_widget_add_css_class(card, "locked"); + gtk_widget_set_sensitive(card, FALSE); + lock = icon_area_new(draw_lock_icon, + 0.3451, 0.3569, 0.4392, 26); + gtk_button_set_child(GTK_BUTTON(card), lock); + } + + sub_cells[i] = card; + gtk_fixed_put(GTK_FIXED(fixed), card, 0, 0); + + lbl = gtk_label_new(NULL); + i18n_bind(lbl, sub_keys[i], 0); + gtk_widget_set_size_request(lbl, (int)(SUB_SPAC - 24.0), -1); + gtk_widget_set_halign(lbl, GTK_ALIGN_CENTER); + gtk_label_set_justify(GTK_LABEL(lbl), GTK_JUSTIFY_CENTER); + gtk_label_set_wrap(GTK_LABEL(lbl), TRUE); + gtk_widget_add_css_class(lbl, "unit-name"); + if (i != 0) + gtk_widget_add_css_class(lbl, "unit-name-locked"); + sub_labels[i] = lbl; + gtk_fixed_put(GTK_FIXED(fixed), lbl, 0, 0); + } + + ha = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(scroll)); + va = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(scroll)); + g_signal_connect(ha, "notify::page-size", + G_CALLBACK(sub_adjust_notify), NULL); + g_signal_connect(va, "notify::page-size", + G_CALLBACK(sub_adjust_notify), NULL); + + sub_apply_layout(); + sub_relayout_later(); + + return page; +} + + /* ------------------------------------------------------------------ */ /* Unit 1 exercise path (roadmap-style serpentine) */ /* ------------------------------------------------------------------ */ @@ -6063,12 +6433,12 @@ static void activate(GtkApplication *app, gpointer user_data) { window = GTK_WINDOW(gtk_application_window_new(app)); main_window = window; - gtk_window_set_title(window, "Sprechen.c"); + gtk_window_set_title(window, "maturita.c"); gtk_window_set_default_size(window, 760, 560); headerbar = gtk_header_bar_new(); gtk_widget_add_css_class(headerbar, "titlebar"); - title_label = gtk_label_new("Sprechen.c"); + title_label = gtk_label_new("maturita.c"); gtk_widget_add_css_class(title_label, "app-title"); gtk_header_bar_set_title_widget(GTK_HEADER_BAR(headerbar), title_label); gtk_window_set_titlebar(window, headerbar); @@ -6083,6 +6453,7 @@ static void activate(GtkApplication *app, gpointer user_data) { welcome_page = build_welcome_page(); roadmap_page = build_roadmap_page(); gtk_stack_add_named(main_stack, welcome_page, "welcome"); + gtk_stack_add_named(main_stack, build_subjects_page(), "subjects"); gtk_stack_add_named(main_stack, roadmap_page, "roadmap"); for (int i = 0; i < NUM_UNLOCKED; i++) { @@ -6123,7 +6494,7 @@ int main(int argc, char **argv) { GtkApplication *app; int status; - app = gtk_application_new("org.example.Sprechen", G_APPLICATION_DEFAULT_FLAGS); + app = gtk_application_new("org.example.maturita", G_APPLICATION_DEFAULT_FLAGS); g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); status = g_application_run(G_APPLICATION(app), argc, argv); g_object_unref(app);