Yeezus
Yeezus

Reputation: 402

GTK 4 Key Press Event Handler

I'm trying to handle a key press event using gtk 4 in c (arrow keys to be specific). Whenever I use some answers from places like here (stackoverflow) and here (stackoverflow) they seem not to work.

Instead I get the following error:

(<unknown>:10924): GLib-GObject-WARNING **: 15:26:27.129: ../gobject/gsignal.c:2613: signal 'key-press-event' is invalid for instance '0x7faa268722f0' of type 'GtkApplicationWindow'

I've tried also tried using 'key_pressed' etc. to no avail.

The overall goal of the following project was to be able to move one square in the direction of the pressed/pressing arrow key, however I haven't got that far because of the small hiccup.

Any advice?

#include <gtk/gtk.h>

static void
draw_function
(GtkDrawingArea *area, cairo_t *cr, int width, int height, gpointer user_data)
{
   // int square_size = 80.0;
   cairo_set_source_rgb (cr, 1.0, 1.0, 1.0); /* white */
   cairo_paint (cr);

   cairo_set_source_rgb(cr, 0.2, 0.3, 0.8);
   cairo_rectangle(cr, 10, 10, 90, 90);
   cairo_fill(cr);
   cairo_save(cr);

   cairo_scale(cr, 0.6, 0.6);
   cairo_set_source_rgb(cr, 0.8, 0.3, 0.2);
   cairo_rectangle(cr, 30, 30, 90, 90);
   cairo_fill(cr);
   cairo_restore(cr);
   cairo_save(cr);

   cairo_scale(cr, 0.8, 0.8);
   cairo_set_source_rgb(cr, 0.8, 0.8, 0.2);
   cairo_rectangle(cr, 50, 50, 90, 90);
   cairo_fill(cr);
   cairo_restore(cr);
}

// 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,GdkEvent *event,gpointer data)
{
   g_print("GTK Application is activated\n");
  return FALSE; //keep processing event
}

static void
activate
(GtkApplication *app, gpointer user_data)
{
   GtkWidget *window;
   GtkWidget *drawingarea;
   static gboolean key_pressed = FALSE, *p_key_pressed = &key_pressed;

   window = gtk_application_window_new(app);

   drawingarea = gtk_drawing_area_new();
   button = gtk_button_new_with_label("Hello World");

   gtk_window_set_title (GTK_WINDOW(window), "Window");
   gtk_window_set_default_size(GTK_WINDOW(window), 400, 400);
   // gtk_widget_set_events(window, GDK_KEY_PRESS);
   gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(drawingarea), draw_function, NULL, NULL);

   g_signal_connect(G_OBJECT(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(button, "clicked", G_CALLBACK (print_hello), NULL);
   gtk_window_set_child(GTK_WINDOW(window), drawingarea);
   gtk_widget_show (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;
}

Upvotes: 1

Views: 2404

Answers (1)

Mohammed Sadiq
Mohammed Sadiq

Reputation: 976

As suggested in one if the link, you should use GtkEventControllerKey to handle key press/release events. Here is an example that would move a square box on arrow keys:

/* event-controller.c
 *
 * Compile: cc -ggdb event-controller.c -o event-controller $(pkg-config --cflags --libs gtk4) -o event-controller
 * Run: ./event-controller
 *
 * Author: Mohammed Sadiq <www.sadiqpk.org>
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later OR CC0-1.0
 */

#include <gtk/gtk.h>

#define REPEAT_MS 100

int x_offset, y_offset;
guint key_repeat_id;
guint key_val;

gboolean
update_square (gpointer user_data)
{
  GtkWidget *drawing_area = user_data;

  g_assert (GTK_IS_DRAWING_AREA (drawing_area));

  if (key_val == GDK_KEY_Left)
    x_offset--;
  else if (key_val == GDK_KEY_Right)
    x_offset++;
  else if (key_val == GDK_KEY_Up)
    y_offset--;
  else if (key_val == GDK_KEY_Down)
    y_offset++;

  /* Request to redraw */
  gtk_widget_queue_draw (drawing_area);

  return G_SOURCE_CONTINUE;
}

static gboolean
event_key_pressed_cb (GtkWidget             *drawing_area,
                      guint                  keyval,
                      guint                  keycode,
                      GdkModifierType        state,
                      GtkEventControllerKey *event_controller)
{
  g_assert (GTK_IS_DRAWING_AREA (drawing_area));

  if (state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_ALT_MASK))
      return FALSE;

  key_val = keyval;

  g_clear_handle_id (&key_repeat_id, g_source_remove);
  key_repeat_id = g_timeout_add (REPEAT_MS, update_square, drawing_area);
  update_square (drawing_area);

  return TRUE;
}

static gboolean
event_key_released_cb (GtkWidget *drawing_area)
{
  /* Stop moving the square regardless of which key was released */
  g_clear_handle_id (&key_repeat_id, g_source_remove);

  return FALSE;

}

static void
draw_func (GtkDrawingArea *drawing_area,
           cairo_t        *cr,
           int             width,
           int             height,
           gpointer        user_data)
{
  int x, y;

  cairo_set_source_rgb (cr, 1.0, 1.0, 1.0); /* white */
  cairo_paint (cr);


  x = CLAMP (10 + x_offset, 0, width - 90);
  y = CLAMP (10 + y_offset, 0, height - 90);

  cairo_set_source_rgb (cr, 0.2, 0.3, 0.8);
  cairo_rectangle (cr, x, y, 90, 90);
  cairo_fill (cr);
  cairo_save (cr);
}

static void
app_activated_cb (GtkApplication *app)
{
  GtkEventController *event_controller;
  GtkWindow *window;
  GtkWidget *child;

  window = GTK_WINDOW (gtk_application_window_new (app));
  gtk_window_set_default_size (window, 360, 540);

  child = gtk_drawing_area_new ();
  gtk_window_set_child (window, child);
  gtk_drawing_area_set_draw_func (GTK_DRAWING_AREA (child),
                                  draw_func,
                                  app, NULL);

  event_controller = gtk_event_controller_key_new ();

  g_signal_connect_object (event_controller, "key-pressed",
                           G_CALLBACK (event_key_pressed_cb),
                           child, G_CONNECT_SWAPPED);
  g_signal_connect_object (event_controller, "key-released",
                           G_CALLBACK (event_key_released_cb),
                           child, G_CONNECT_SWAPPED);
  gtk_widget_add_controller (GTK_WIDGET (window), event_controller);

  gtk_window_present (window);
}

int
main (int   argc,
      char *argv[])
{
  g_autoptr(GtkApplication) app = gtk_application_new (NULL, 0);

  g_signal_connect (app, "activate", G_CALLBACK (app_activated_cb), NULL);

  return g_application_run (G_APPLICATION (app), argc, argv);
}

Upvotes: 5

Related Questions