0
votes

Edit: I have found a solution for the simple example below, but it fails for my real application, and generates a critical warning on the example below. Through trial and error, I discovered that inserting gtk_main_quit () at the end of the activate function successfully blocks the initial command line "Return". However, of course, it also generates a critical warning: Gtk-CRITICAL **: gtk_main_quit: assertion 'main_loops != NULL' failed. Another thing that works for the example but I don't want in my application is to put up a "hello" dialog in the activate function which the user has to dismiss.

I am working on a Gtk3 app that listens for the key-release-event, and I would like one of those events to be the "Return" key. The app will normally be started from a .desktop file, but may be started from a terminal too. When started from a terminal, it recognizes the initial "Return" that the user pressed to execute the app as a key-press. How can I avoid that? Here is a simple example "test_return.c"

/* gcc `pkg-config --cflags gtk+-3.0` -o test_return test_return.c `pkg-config --libs gtk+-3.0`
 */
#include <gtk/gtk.h>

static gboolean key_event_cb (GtkWidget *widget,
                            GdkEventKey *event,
                                gpointer data)
{
  if (event->keyval == GDK_KEY_Return) {
    GtkWidget *dialog = 
      gtk_message_dialog_new (GTK_WINDOW(widget), 
          GTK_DIALOG_DESTROY_WITH_PARENT, 
          GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE, 
          "Return");
    gtk_dialog_run (GTK_DIALOG (dialog));
    gtk_widget_destroy (dialog);
    return TRUE;
  }
  return FALSE; //allow further processing of the keypress if it's not Return
}

static void activate (GtkApplication* app,
                      gpointer        user_data)
{  
  GtkWidget *window;

  window = gtk_application_window_new (app);
  gtk_widget_show_all (window);
  g_signal_connect (window, "key-release-event", 
                    G_CALLBACK (key_event_cb), NULL); //keyboard events
}

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

  app = gtk_application_new ("org.gtk.example", G_APPLICATION_FLAGS_NONE);
  g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);
  status = g_application_run (G_APPLICATION (app), 0, NULL);
  g_object_unref (app);

  return status;
}

What I want to happen is that it will not pop up a dialog until "Return" is pressed. When run from a terminal, it immediately shows the dialog because it recognizes the "Return" pressed in the terminal to start the app. When run from a .desktop file, it works as expected.

At first, I tried simply defining a gboolean that was TRUE at start, and FALSE after the first Return. That worked from the terminal, but then requred 2 Return key-presses when started from a .desktop file. My app will only be used in Linux (and in fact requires X11). I tried fflush(stdin);, but it doesn't work. I even tried a timer to ignore Returns received in the first hundreth of a second using https://developer.gnome.org/glib/stable/glib-Timers.html, but I get strange behavior.

From the comments, I remembered that I've already tried waiting to connect the signal until the widget is fully drawn. It didn't work. Apparently, the "Return" key press is waiting in a buffer, but I can't figure out how to clear that buffer.

Also from the comments, I remembered that I already tried getchar() but that just waited for a character to be entered, completely ignoring the Return that I was trying to get rid of.

I edited main() replacing argc, argv with 0, NULL in the g_application_run funtion to ignore command line arguments, but that didn't help.

1
Have you considered just consuming the first key release event the application sees when the indicated key is the return key? Or consuming such events that arrive within some short time of application startup? These are a bit hacky, but they ought to get the job done.John Bollinger
How about watching for some other event, and using that to turn on the key-watching event? For example, you could watch for "Window Mapped" or "Refresh" (I'm not a GTK programmer, but there must be events like that). Once you see "Window Mapped," you know that the program is up and running, and would want to process any subsequent Return key-ups.Dave M.
Perhaps you can move the goal posts and use a different key.Weather Vane
How to consume it? I tried fflush(stdin) and also tried a single getchar() as a test, and it didn't consume the Return. Instead, it waited for me to enter a character. As mentioned in my question, I also tried waiting a hundredth of a second. This worked from the terminal, but caused strange behavior from a .desktop file which I haven't been able to debug.Colin Keenan
Ah, I see, it just closes the dialog, and if you type return again, it opens. I thought the whole app would close.Dave M.

1 Answers

2
votes

As I was reading through all the GdkEvent functions: https://developer.gnome.org/gdk3/stable/gdk3-Events.html, I eventually realized it might be informative to use gdk_set_show_events (TRUE) so that as the program runs, all the events are listed in the terminal. There were tons of events, but just searching on the word "key" I eventually realized something very important. There is a difference between the Return key event from the command line and all the other Return key events: the one from the command line doesn't have a key-press-event, only a key-release event! So, here's how the solution works for the example shown in my question:

/* gcc `pkg-config --cflags gtk+-3.0` -o test_return test_return.c `pkg-config --libs gtk+-3.0`
 */
#include <gtk/gtk.h>

// Need to keep track of both key press and key release to distinguish unwanted terminal Return from others.
// The terminal Return that started the app won't have a key press event.
static gboolean key_press_event_cb (GtkWidget *widget,
                            GdkEventKey *event,
                                gpointer data)
{
  gboolean *p_key_pressed = data;
  *p_key_pressed = TRUE;
  return FALSE; //keep processing event
}

static gboolean key_release_event_cb (GtkWidget *widget,
                            GdkEventKey *event,
                                gpointer data)
{
  gboolean *p_key_pressed = data;
  // If there's no key-press before this key-release, then it's from starting the app in a terminal
  // So ignore it.
  if (*p_key_pressed & event->keyval == GDK_KEY_Return) {
    GtkWidget *dialog = 
      gtk_message_dialog_new (GTK_WINDOW(widget), 
          GTK_DIALOG_DESTROY_WITH_PARENT, 
          GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE, 
          "time is %d", event->time);
    gtk_dialog_run (GTK_DIALOG (dialog));
    gtk_widget_destroy (dialog);
    return TRUE;
  }
  return FALSE; //allow further processing of the keypress if it's not Return
}

static void activate (GtkApplication* app,
                      gpointer        user_data)
{  
  GtkWidget *window;
  // create a pointer to a gboolean to track the key press events
  static gboolean key_pressed = FALSE, *p_key_pressed = &key_pressed;

  window = gtk_application_window_new (app);

  g_signal_connect (window, "key-press-event", 
                    G_CALLBACK (key_press_event_cb), p_key_pressed); // p_key_pressed will be the "data" in cb function
  g_signal_connect (window, "key-release-event", 
                    G_CALLBACK (key_release_event_cb), p_key_pressed); // p_key_pressed will be the "data" in cb function

  gtk_widget_show_all (window);
}

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

  app = gtk_application_new ("org.gtk.example", G_APPLICATION_FLAGS_NONE);
  g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);
  status = g_application_run (G_APPLICATION (app), 0, NULL);
  g_object_unref (app);

  return status;
}