vmagnin/gtk-fortran

Calling gtk_application_set_accels_for_action causes a crash

jnez137 opened this issue · 14 comments

Describe the bug
This is related to gtk4 version of gtk-fortran. No matter what I have tried, every time i call gtk_application_set_accels_for_action on macOS, my application crashes with a stack trace like this, failing in trying to parse the accelerator.

Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
0 libsystem_platform.dylib 0x00007fff6b896e52 platform_strlen + 18
1 libgtk-4.1.dylib 0x0000000115e8e7d4 gtk_accelerator_parse_with_keycode + 99
2 libgtk-4.1.dylib 0x0000000116020560 gtk_application_accels_set_accels_for_action + 292
3 libgtk-4.1.dylib 0x0000000115e9498c gtk_application_set_accels_for_action + 103
4 com.jeremy.Zoa 0x000000010b69336e MAIN
_ + 1066 (zoamain.F90:52)
5 com.jeremy.Zoa 0x000000010b69347f main + 54 (zoamain.F90:7)
6 libdyld.dylib 0x00007fff6b6a0cc9 start + 1

Expected behavior
At this point, I would just like the app to not crash. My goal in using this function was to try to assign mac specific keyboard accelerators to my gtk4 application (eg q to quit the app instead of q). I was able to get this to work within menus by modifying actions there, but the shortcuts only seem to work when I click on the menu and it appears that the _set_accels_for_action is a more general solution from the gtk documentation.

To Reproduce

  1. Take any of the gtk-4-fortran examples.
  2. Add a line of code like this after gtk_application_new has been called.
    call gtk_application_set_accels_for_action(app, "quit"//c_null_char, "q"//c_null_char)
    where app is what was returned when gtk_application_new was called.
  3. Compile and run the program. it will crash when this is called. Note: The exact text in the third argument doesn't seem to matter, as long as you pass anything other than c_null_char it will crash.

Your system:

  • OS version: Tried MacOS 10.15 and 12.15 (two different machines). Also crashes in Windows.
  • Compiler version: gfortran 12 (also crashes using latest version of ifort in windows).
  • GTK branch: [e.g gtk4] gtk4, latest version released via homebrew.

Additional context
It's possible I am doing something wrong, but I haven't been able to find it. Also in looking through GitHub, it seems there are other apps using this interface in python and rust apps, which makes me think that it should be possible to get it to work with a language binding. Also, the fact that it crashes while trying to parse a string makes me wonder if there is something wrong with the binding interface.

On that note, I did look through the interface definitions in trying to figure out what was going on and I noticed that you are mapping const char * const *accels to a c_char, dimension(:) type.


! GDK_AVAILABLE_IN_ALL
!void gtk_application_set_accels_for_action (GtkApplication *application, const char detailed_action_name, const char * const accels);
subroutine gtk_application_set_accels_for_action(application,&
& detailed_action_name, accels) bind(c)
import :: c_ptr, c_char
implicit none
type(c_ptr), value :: application
character(kind=c_char), dimension(
) :: detailed_action_name
character(kind=c_char), dimension(
) :: accels
end subroutine


But when that same type is returned from a function, you map it to a c_ptr, such as here:


! GDK_AVAILABLE_IN_ALL
!const char * const * gtk_about_dialog_get_authors (GtkAboutDialog *about);
function gtk_about_dialog_get_authors(about) bind(c)
import :: c_ptr
implicit none
type(c_ptr) :: gtk_about_dialog_get_authors
type(c_ptr), value :: about
end function


So was wondering if you had figured out the hard way that these were the proper type translations depending on whether they were a function argument or returned from a function.

Anyway, any insight that can be provided would be helpful.

Thanks @jnez137 for reporting,

I have edited my message, as finally, yes,

call gtk_application_set_accels_for_action(app, "quit"//c_null_char, "q"//c_null_char) does also crash in Linux Ubuntu:

$ ./bazaar

Program received signal SIGSEGV: Segmentation fault - invalid memory reference.

Backtrace for this error:
#0  0x7fe015023970 in ???
#1  0x7fe015022ad5 in ???
#2  0x7fe014c3c4af in ???
        at ./signal/../sysdeps/unix/sysv/linux/x86_64/libc_sigaction.c:0
#3  0x7fe014cb39fa in __strlen_sse2
        at ../sysdeps/x86_64/multiarch/strlen-sse2.S:142
#4  0x7fe0156bdfed in ???
#5  0x7fe0156c266a in ???
#6  0x5571da548657 in ???
#7  0x7fe014c23a8f in __libc_start_call_main
        at ../sysdeps/nptl/libc_start_call_main.h:58
#8  0x7fe014c23b48 in __libc_start_main_impl
        at ../csu/libc-start.c:360
#9  0x5571da5486d4 in ???
#10  0xffffffffffffffff in ???
Erreur de segmentation (core dumped)

I will investigate about const char * const *, as it appears in ~13 GTK functions. Thanks for pointing these types.

I have also found but don't know if it is related:
https://gitlab.gnome.org/GNOME/gtk/-/issues/2533

That apart, reading https://docs.gtk.org/gtk4/func.accelerator_parse.html
it is not clear for me if qis a valid accelerator or if a modifier key must be included: <Ctrl>q, <Alt>q...

@jnez137 If you have received the first version of my previous message, read the edited version.

On that page https://docs.gtk.org/gtk4/method.Application.set_accels_for_action.html
we can see accels is in fact an array of strings (char*), not a string.

zero or more keyboard accelerators that will trigger the given action

Other gtk-fortran functions using const char * const *

$ ack "const char \* const \*"
gtk-4-fortran-index.csv
645:g;g_assertion_message_cmpstrv;GLIB_AVAILABLE_IN_2_68;glib-auto.f90;/usr/include/glib-2.0/glib/gtestutils.h;"void g_assertion_message_cmpstrv (const char *domain, const char *file, int line, const char *func, const char *expr, const char * const *arg1, const char * const *arg2, gsize first_wrong_idx) ;";subroutine g_assertion_message_cmpstrv(domain, file, line, func, expr, arg1, arg2, first_wrong_idx) bind(c)
4646:gdk;gdk_content_formats_get_mime_types;GDK_AVAILABLE_IN_ALL;gdk-auto.f90;/usr/include/gtk-4.0/gdk/gdkcontentformats.h;"const char * const * gdk_content_formats_get_mime_types (const GdkContentFormats *formats, gsize *n_mime_types);";function gdk_content_formats_get_mime_types(formats, n_mime_types) bind(c)
5954:gtk;gtk_about_dialog_get_artists;GDK_AVAILABLE_IN_ALL;gtk-auto.in;/usr/include/gtk-4.0/gtk/gtkaboutdialog.h;"const char * const * gtk_about_dialog_get_artists (GtkAboutDialog *about);";function gtk_about_dialog_get_artists(about) bind(c)
5955:gtk;gtk_about_dialog_get_authors;GDK_AVAILABLE_IN_ALL;gtk-auto.in;/usr/include/gtk-4.0/gtk/gtkaboutdialog.h;"const char * const * gtk_about_dialog_get_authors (GtkAboutDialog *about);";function gtk_about_dialog_get_authors(about) bind(c)
5958:gtk;gtk_about_dialog_get_documenters;GDK_AVAILABLE_IN_ALL;gtk-auto.in;/usr/include/gtk-4.0/gtk/gtkaboutdialog.h;"const char * const * gtk_about_dialog_get_documenters (GtkAboutDialog *about);";function gtk_about_dialog_get_documenters(about) bind(c)
6046:gtk;gtk_alert_dialog_get_buttons;;gtk-auto.in;/usr/include/gtk-4.0/gtk/gtkalertdialog.h;"const char * const * gtk_alert_dialog_get_buttons (GtkAlertDialog *self);";function gtk_alert_dialog_get_buttons(self) bind(c)
6052:gtk;gtk_alert_dialog_set_buttons;GDK_AVAILABLE_IN_4_10;gtk-auto.in;/usr/include/gtk-4.0/gtk/gtkalertdialog.h;"void gtk_alert_dialog_set_buttons (GtkAlertDialog *self, const char * const *labels);";subroutine gtk_alert_dialog_set_buttons(self, labels) bind(c)
6113:gtk;gtk_application_set_accels_for_action;GDK_AVAILABLE_IN_ALL;gtk-auto.in;/usr/include/gtk-4.0/gtk/gtkapplication.h;"void gtk_application_set_accels_for_action (GtkApplication *application, const char *detailed_action_name, const char * const *accels);";subroutine gtk_application_set_accels_for_action(application, detailed_action_name, accels) bind(c)
6734:gtk;gtk_drop_down_new_from_strings;GDK_AVAILABLE_IN_ALL;gtk-auto.in;/usr/include/gtk-4.0/gtk/gtkdropdown.h;"GtkWidget * gtk_drop_down_new_from_strings (const char * const * strings);";function gtk_drop_down_new_from_strings(strings) bind(c)
7363:gtk;gtk_icon_theme_set_resource_path;GDK_AVAILABLE_IN_ALL;gtk-auto.in;/usr/include/gtk-4.0/gtk/gtkicontheme.h;"void gtk_icon_theme_set_resource_path (GtkIconTheme *self, const char * const *path);";subroutine gtk_icon_theme_set_resource_path(self, path) bind(c)
7364:gtk;gtk_icon_theme_set_search_path;GDK_AVAILABLE_IN_ALL;gtk-auto.in;/usr/include/gtk-4.0/gtk/gtkicontheme.h;"void gtk_icon_theme_set_search_path (GtkIconTheme *self, const char * const *path);";subroutine gtk_icon_theme_set_search_path(self, path) bind(c)
8616:gtk;gtk_string_list_new;GDK_AVAILABLE_IN_ALL;gtk-auto.in;/usr/include/gtk-4.0/gtk/gtkstringlist.h;"GtkStringList * gtk_string_list_new (const char * const *strings);";function gtk_string_list_new(strings) bind(c)
8618:gtk;gtk_string_list_splice;GDK_AVAILABLE_IN_ALL;gtk-auto.in;/usr/include/gtk-4.0/gtk/gtkstringlist.h;"void gtk_string_list_splice (GtkStringList *self, guint position, guint n_removals, const char * const *additions);";subroutine gtk_string_list_splice(self, position, n_removals, additions) bind(c)

In examples/bazaar.f90 I have found this piece of code:

    ! To add authors we need a pointer toward a null terminated array of strings.
    ! This code comes from src/gtk-hl-dialog.f90:
    allocate(c_ptr_array(size(authors)+1))

    do i = 1, size(authors)
      call f_c_string(authors(i), string)
      allocate(credit(size(string)))
      ! A Fortran pointer toward the Fortran string:
      credit(:) = string(:)
      ! Store the C address in the array:
      c_ptr_array(i) = c_loc(credit(1))
      nullify(credit)
    end do
    ! The array must be null terminated:
    c_ptr_array(size(authors)+1) = c_null_ptr
    ! https://docs.gtk.org/gtk3/method.AboutDialog.set_authors.html
    call gtk_about_dialog_set_authors(dialog, c_ptr_array)
    deallocate(c_ptr_array)

and in src/gtk.in:

! GDK_AVAILABLE_IN_ALL
!void gtk_about_dialog_set_authors (GtkAboutDialog *about, const char **authors);
subroutine gtk_about_dialog_set_authors(about, authors) bind(c)
  import :: c_ptr
  implicit none
  type(c_ptr), value :: about
  type(c_ptr), dimension(*) :: authors
end subroutine

So I think you can try to modify character(kind=c_char), dimension(*) :: accels in src/gtk.in to type(c_ptr), dimension(*) :: accels, then use a piece of code inspired by the one found in bazaar.f90 (you don't really need the loop in your case).

I will have to investigate into the cfwrapper script to make gtk-fortran correctly deal with const char * const * (that may be simple and quick, or not...)

An idea to go further when things will be fixed: we could try to add in gtk-sup.f90 an utilitary function that returns such a null terminated array of strings.

In src/cfwrapper/fortran.py, the job is done here (line 69):

    # Is it a pointer ?
    if "*" in declaration:
        # Is it a string (char or gchar array) ?
        if ("char" in c_type) and (not isReturned):
            if  "**" in declaration:
                return "type(c_ptr), dimension(*)", "c_ptr"
            else:
                return "character(kind=c_char), dimension(*)", "c_char"
        else:
            return "type(c_ptr)", "c_ptr"

Modifying the third if like this could fix the problem:

            if ("**" in declaration) or ("* const *" in declaration) :

But I have found also one occurrence of *const * in g_settings_set_strv(). I will design a regex to do a clean job.

Hi Vincent,

I tried your fix, and at least with using a simple test program with a debugger, was able to confirm the correct data was being sent to the c function. See attached screenshot - top is a pure c test program, bottom is modifying the gtkhello example to call the set_accels method after the app is run and sending it a null pointer terminated string. Previously I was getting garbage in the fortran program and that is why it was crashing.
Now I can go about figuring out if I am using the method correctly or not..
Screen Shot 2023-08-29 at 12 17 38 AM

Hi Jeremy @jnez137 ,

in the development branch gtk4-vmagnin, I have modified the Python line like this:

        if declaration.count('*') >= 2:

and regenerated the gtk-fortran *-auto files. See this commit e55b6b5

gtk_application_set_accels_for_action is part of the functions that have been fixed.

It would be great if you could build that gtk4-vmagnin branch and test it with your code. I could then merge it into the main branch.

Hi Vincent,

Thanks! Yes I can confirm it fixed the issue. For what it's worth, here is the code snippet that is now working (sorry it's a bit messy). I had to add a quit action to the app and then attach the accelerator to it. But once I did this not only did the program not crash but it did what I wanted :)

!/* quit */
quit_action = g_simple_action_new("quit"//c_null_char, c_null_ptr);
call g_signal_connect(quit_action, "activate"//c_null_char, &
& c_funloc(destroy_signal), app)
call g_action_map_add_action(app,quit_action)

call f_c_string("<meta>q"//c_null_char, stringtmp)
allocate(credit(size(stringtmp)))
 ! A Fortran pointer toward the Fortran string:
credit(:) = stringtmp(:)
 ! Store the C address in the array:
 c_ptr_array(1) = c_loc(credit(1))
 nullify(credit)

! The array must be null terminated:
c_ptr_array(2) = c_null_ptr


call gtk_application_set_accels_for_action(app, "app.quit"//c_null_char, c_ptr_array)

Thanks a lot Jeremy for testing and for the code snippet. I will try to use similar code in the examples/menubar.f90 gtk-fortran example, as it is demonstrating GTK/GLib actions.

Jeremy,
I have merged the code in gtk4 and made a gtk-fortran 4.4.1 release.

I will also open an issue for an improvement about writing an auxiliary function to create those arrays of C strings.

See #278