[GTK4 C] every X launchs app window doesn't show

I’m making a C Application using GtkApplication, trying to use standard way to do so, but randomly my application doesn’t show and needs to be killed, then immediately of after several kills shows and runs ok.

It’s a simple project with only one c file, main.c

int main (int argc, char ** argv) {
  GtkApplication * app;
  donneesDep * monDepliage;
  int status;

  monDepliage = g_new (donneesDep, 1);
  donneesDepliageInit (monDepliage);

  app = gtk_application_new (APPLICATION_ID, G_APPLICATION_DEFAULT_FLAGS);
  g_signal_connect (app, "activate", G_CALLBACK (initUI), monDepliage);
  status = g_application_run (G_APPLICATION (app), argc, argv);

  donneesDepliageFree (monDepliage);
  g_free (monDepliage);
  g_object_unref (app);

  return status;
}

a function to respond to app.activate signal and where I create the application window then populate it with UI elements, setup callbacks nd finally show the window. It’s from that point where from time to time the app doesn’t work.

I added an explicite gtk_window_destroy before the g_application_quit on the callback function called by the menu quit option.

Any idea is welcome, I tried a gdb session, but I am not experienced enough with it.

I tried to free any resource that I used. What is odd is that even if I don’t do anything sometimes after I quit, next run hangs instead of showing the window. Maybe is this behaviour linked to the way I setup my UI, here it is

// Initialise l'interface utilisateur et l'active
static void initUI (GApplication * app, gpointer data) {
  donneesDep * donnees = (donneesDep *)data;
  GtkWidget * header, * button, * box;

  lmenu lm[] = {
    {0, "zout", "Dézoomer", "win.zout", actionZoomOut},
    {0, "znor", "Zoom 100%", "win.znor", actionZoomNormal},
    {0, "zin",  "Zoomer", "win.zin", actionZoomIn},
    {1, "new",  "Nouveau dépliage", "win.new", actionNouveau},
    {1, "open", "Ouvrir dépliage", "win.open", actionOuvre},
    {2, "save", "Sauver", "win.save", actionSauve},
    {3, "expg", "Exporter gabarit", "win.expg", actionExporteGabarit},
    {4, "help", "Aide", "app.help", actionAide},
    {4, "quit", "Quitter", "app.quit", actionQuitte}
  };

  win = gtk_application_window_new (GTK_APPLICATION(app));
  gtk_window_set_title (GTK_WINDOW (win), "Deplieur Base");
  gtk_window_set_default_size (GTK_WINDOW (win), 600, 400);
  //g_signal_connect (win, "destroy", G_CALLBACK (close_window), NULL);

  header = gtk_header_bar_new ();
  button = gtk_menu_button_new ();
  gtk_menu_button_set_icon_name (GTK_MENU_BUTTON (button), "open-menu-symbolic");
  gtk_widget_set_tooltip_text (button, "Menu");
  gtk_menu_button_set_menu_model (GTK_MENU_BUTTON (button), G_MENU_MODEL (creeMenu(app, win, lm, 9, donnees)));
  gtk_header_bar_pack_end (GTK_HEADER_BAR (header), button);

  box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
  gtk_widget_add_css_class (box, "linked");
  button = gtk_button_new_from_icon_name ("document-new");
  gtk_box_append (GTK_BOX (box), button);

  gtk_header_bar_pack_start (GTK_HEADER_BAR (header), box);
  gtk_window_set_titlebar (GTK_WINDOW (win), header);

  GtkWidget * cadre = gtk_scrolled_window_new();
  rendu = gtk_drawing_area_new();
  gtk_widget_set_cursor(rendu, gdk_cursor_new_from_name("crosshair", NULL));

  gtk_drawing_area_set_content_width(GTK_DRAWING_AREA(rendu), (int)formats[donnees->formatPage].x);
  gtk_drawing_area_set_content_height(GTK_DRAWING_AREA(rendu), (int)formats[donnees->formatPage].y);
  gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(cadre), rendu);
  gtk_widget_set_vexpand(cadre, TRUE);
  gtk_widget_set_hexpand(cadre, TRUE);
  donnees->surface = cairo_pdf_surface_create("work.pdf",
    formats[donnees->formatPage].x, formats[donnees->formatPage].y);

  gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(rendu), dessinePage, donnees, NULL);

  GtkEventController * hover = gtk_event_controller_motion_new ();
  gtk_widget_add_controller (rendu, hover);
  g_signal_connect (hover, "motion", G_CALLBACK (gereHover), donnees );

  GtkWidget *bv = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
  gtk_window_set_child(GTK_WINDOW(win), bv);
  gtk_box_append(GTK_BOX(bv), cadre);

  statut = gtk_statusbar_new();
  gtk_box_append (GTK_BOX (bv), statut);

  g_print("status bar OK\n");

  gtk_widget_show (win); // active l'application
  g_print("apres show win\n");
}

By default, when your application is launched but an instance is already running, the already running instance will be notified and its “activate” signal handler will be run.

So the “activate” handler can run multiple times! For sure one time during startup, but other invocations may follow.

So in your initUI function check whether win is NULL. If it’s NULL, then the application is starting up: construct the UI like you’re doing already. If it’s not NULL, then the UI is already set up! In that case just call gtk_window_present (GTK_WINDOW (win)) to present the window to the user.

// Initialise l'interface utilisateur et l'active
static void initUI (GApplication * app, gpointer data) {
  if (win != NULL)
    {
      gtk_window_present (GTK_WINDOW (win));
      return;
    }

  donneesDep * donnees = (donneesDep *)data;
  GtkWidget * header, * button, * box;

  lmenu lm[] = {
    {0, "zout", "Dézoomer", "win.zout", actionZoomOut},
    {0, "znor", "Zoom 100%", "win.znor", actionZoomNormal},
    {0, "zin",  "Zoomer", "win.zin", actionZoomIn},
    {1, "new",  "Nouveau dépliage", "win.new", actionNouveau},
    {1, "open", "Ouvrir dépliage", "win.open", actionOuvre},
    {2, "save", "Sauver", "win.save", actionSauve},
    {3, "expg", "Exporter gabarit", "win.expg", actionExporteGabarit},
    {4, "help", "Aide", "app.help", actionAide},
    {4, "quit", "Quitter", "app.quit", actionQuitte}
  };

  win = gtk_application_window_new (GTK_APPLICATION(app));
  gtk_window_set_title (GTK_WINDOW (win), "Deplieur Base");
  gtk_window_set_default_size (GTK_WINDOW (win), 600, 400);
  //g_signal_connect (win, "destroy", G_CALLBACK (close_window), NULL);

  header = gtk_header_bar_new ();
  button = gtk_menu_button_new ();
  gtk_menu_button_set_icon_name (GTK_MENU_BUTTON (button), "open-menu-symbolic");
  gtk_widget_set_tooltip_text (button, "Menu");
  gtk_menu_button_set_menu_model (GTK_MENU_BUTTON (button), G_MENU_MODEL (creeMenu(app, win, lm, 9, donnees)));
  gtk_header_bar_pack_end (GTK_HEADER_BAR (header), button);

  box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
  gtk_widget_add_css_class (box, "linked");
  button = gtk_button_new_from_icon_name ("document-new");
  gtk_box_append (GTK_BOX (box), button);

  gtk_header_bar_pack_start (GTK_HEADER_BAR (header), box);
  gtk_window_set_titlebar (GTK_WINDOW (win), header);

  GtkWidget * cadre = gtk_scrolled_window_new();
  rendu = gtk_drawing_area_new();
  gtk_widget_set_cursor(rendu, gdk_cursor_new_from_name("crosshair", NULL));

  gtk_drawing_area_set_content_width(GTK_DRAWING_AREA(rendu), (int)formats[donnees->formatPage].x);
  gtk_drawing_area_set_content_height(GTK_DRAWING_AREA(rendu), (int)formats[donnees->formatPage].y);
  gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(cadre), rendu);
  gtk_widget_set_vexpand(cadre, TRUE);
  gtk_widget_set_hexpand(cadre, TRUE);
  donnees->surface = cairo_pdf_surface_create("work.pdf",
    formats[donnees->formatPage].x, formats[donnees->formatPage].y);

  gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(rendu), dessinePage, donnees, NULL);

  GtkEventController * hover = gtk_event_controller_motion_new ();
  gtk_widget_add_controller (rendu, hover);
  g_signal_connect (hover, "motion", G_CALLBACK (gereHover), donnees );

  GtkWidget *bv = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
  gtk_window_set_child(GTK_WINDOW(win), bv);
  gtk_box_append(GTK_BOX(bv), cadre);

  statut = gtk_statusbar_new();
  gtk_box_append (GTK_BOX (bv), statut);

  g_print("status bar OK\n");

  gtk_widget_show (win); // active l'application
  g_print("apres show win\n");
}

That mechanism is very useful for applications that show multiple windows or multiple tabs (like web browsers, etc.). If you don’t want that, just specify G_APPLICATION_NON_UNIQUE instead of G_APPLICATION_DEFAULT_FLAGS

Thank you, I changed the code as you suggested, this way with G_APPLICATION_NON_UNIQUE I can run several projects at the same time, but after several runs / quit, it hangs again.
On this capture, I needed to run it six times before one of them show the window.

I’m now testing the application on Windows after each modification, and on Windows the behaviour is the same, I need to launch the .exe several times to have it show the window. I’m going to restart building this application step-by-step to see where this odd behaviour comes from.

So I restarted building this application step-by-step and what makes it hang is gtk_drawing_area_set_draw_func(), if I disable it the application window correctly show at every run.


gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(rendu), dessinePage, donnees, NULL);

static void dessinePage(GtkDrawingArea* area, cairo_t* cr, int width, int height, gpointer data) {
  donneesDep * donnees = (donneesDep*)data;
  int i, j, j2;

  static const double dashCadre[] = { 10.0, 10.0, 10.0 };

  gdk_cairo_set_source_rgba (cr, &c_BLANC);
  cairo_paint (cr);

  gdk_cairo_set_source_rgba (cr, &c_NOIR);
  cairo_set_line_width (cr, 2 * donnees->fz);
  cairo_set_line_cap  (cr, CAIRO_LINE_CAP_BUTT);
  cairo_set_dash (cr, dashCadre, 1, 0);

  for (i = 0 ; i < donnees->nbPx; i++)
    for (j = 0; j < donnees->nbPy; j++) {
      cairo_rectangle (cr,
        (marge.x + formats[donnees->formatPage].x *i) * donnees->fz,
        (marge.y + formats[donnees->formatPage].y *j) * donnees->fz,
        (formats[donnees->formatPage].x - (marge.x *2)) * donnees->fz,
        (formats[donnees->formatPage].y - (marge.y *2)) * donnees->fz);
      cairo_stroke (cr);
    }

  cairo_set_line_width (cr, 2 * donnees->fz);
  cairo_set_dash (cr, dashCadre, 0, 0);

  _Bool poss;
  vector2d sC1, sC2;
  voisin V = voisinNew(-1, -1);
  int dx, dy;
  triangle2d c;
  vector2d dP;
  GdkRGBA gc;

  for (i = 0; i < donnees->nbFaces; i++) {
    if (donnees->depl[i].page > -1) {
      dx = (int)(donnees->depl[i].page / donnees->nbPy);
      dy = donnees->depl[i].page - (dx * donnees->nbPy);
      dP = vector2dNew (
        (marge.x + formats[donnees->formatPage].x * dx),
        (marge.y + formats[donnees->formatPage].y * dy)
      );
      c = triangle2dMul(triangle2dAdd (donnees->depl[i].v2d, dP), donnees->fz);

      // fond coloré
      gc = donnees->gcoul[donnees->depl[i].groupe];
      cairo_set_source_rgba (cr, gc.red, gc.green, gc.blue, 0.75);
      cairo_move_to_vector2d (cr, c.vec[0]);
      cairo_line_to_vector2d (cr, c.vec[1]);
      cairo_line_to_vector2d (cr, c.vec[2]);
      cairo_close_path (cr);
      cairo_fill (cr);

      // bord noir (ou jaune si sélectionné)
      poss = (donnees->fCourante > -1) && (i == donnees->fCourante) && (donnees->depl[i].page == donnees->pCourante);


      for (j = 0; j < 3; j++) {
        j2 = suiv(j);
        if (poss
         && min(j, j2) == min(donnees->p1Courant, donnees->p2Courant)
         && max(j, j2) == max(donnees->p1Courant, donnees->p2Courant)
        ) {
          gdk_cairo_set_source_rgba (cr, &c_JAUNE);
          cairo_set_line_width (cr, 3 * donnees->fz);
          sC1 = milieu(c.vec[j], c.vec[j2]);
          V = donnees->depl[i].voisins[j];
        } else {
          gdk_cairo_set_source_rgba (cr, &c_NOIR);
          cairo_set_line_width (cr, 2 * donnees->fz);
        }
        cairo_move_to_vector2d (cr, c.vec[j]);
        cairo_line_to_vector2d (cr, c.vec[j2]);
        cairo_stroke (cr);
      }
    }
  }

  // recherche arête liée à la sélection
  if (V.nF > -1) {
    for (i = 0; i < donnees->nbFaces; i++) {
      if (donnees->depl[i].page > -1) {
        dx = (int)(donnees->depl[i].page / donnees->nbPy);
        dy = donnees->depl[i].page - (dx * donnees->nbPy);
        dP = vector2dNew (
          (marge.x + formats[donnees->formatPage].x * dx),
          (marge.y + formats[donnees->formatPage].y * dy)
        );
        c = triangle2dMul(triangle2dAdd (donnees->depl[i].v2d, dP), donnees->fz);
        for (j = 0; j < 3; j++) {
          j2 = suiv(j);
          if (V.nF == i && V.idx == j2) {
            sC2 = milieu(c.vec[j], c.vec[j2]);
          }
        }
      }
    }
    if (donnees->fCourante > -1) {
      // s'il y a une sélection en cours, chercher son partenaire et les relier
      gdk_cairo_set_source_rgba (cr, &c_JAUNE);
      cairo_set_line_width (cr, 3 * donnees->fz);
      cairo_move_to_vector2d (cr, sC1);
      cairo_line_to_vector2d (cr, sC2);
      cairo_stroke (cr);
    }
  }
}

Maybe do I need to put a parameter instead of the latest NULL of gtk_drawing_area_set_draw_func () so that its data can be destroyed ? Or I could destroy what I used to draw at the end of the drawing function, but what ? One thing that can help diagnose my problem is that the application uses lots of memory, easily > 100 Mb. Maybe do I need to replace Cairo by something else, I only need to draw 2d elements, triangles, lines and text (I can replace text if needed by polygons).

Here is what my application is doing for the moment
demi
It opens a 3d model of .obj format, then extract pieces for each material and put them into as many pages as needed. Then the user can edit those pieces. When the mouse hovers over an edge, the corresponding edge is shown. Then if the user clicks the edges comes together or are splitted.