validate: Add a gtk plugins that implements action types relative to Gtk
authorThibault Saunier <tsaunier@gnome.org>
Tue, 21 Apr 2015 13:29:15 +0000 (15:29 +0200)
committerThibault Saunier <tsaunier@gnome.org>
Thu, 14 May 2015 13:56:56 +0000 (15:56 +0200)
Summary:
Currently the only supported action is gtk-put-event allowing press and
release keyboard keys.

Reviewers: Mathieu_Du

Differential Revision: http://phabricator.freedesktop.org/D117

validate/configure.ac
validate/gst/plugins/Makefile.am
validate/gst/plugins/gtk/Makefile.am [new file with mode: 0644]
validate/gst/plugins/gtk/gstvalidategtk.c [new file with mode: 0644]

index 4ff3869..a267d22 100644 (file)
@@ -176,6 +176,15 @@ PKG_CHECK_MODULES(GIO, gio-2.0, HAVE_GIO=yes, HAVE_GIO=no)
 AC_SUBST(GIO_CFLAGS)
 AC_SUBST(GIO_LIBS)
 
+PKG_CHECK_MODULES(GTK, gtk+-3.0, HAVE_GTK=yes, HAVE_GTK=no)
+AC_SUBST(GTK_CFLAGS)
+AC_SUBST(GTK_LIBS)
+AM_CONDITIONAL(HAVE_GTK, test "x$HAVE_GTK" = "xyes")
+
+PKG_CHECK_MODULES(GDK, gdk-3.0, HAVE_GDK=yes, HAVE_GDK=no)
+AC_SUBST(GDK_CFLAGS)
+AC_SUBST(GDK_LIBS)
+
 dnl checks for gstreamer
 
 AG_GST_CHECK_GST_CHECK($GST_API_VERSION, [$GST_REQ], no)
@@ -297,6 +306,7 @@ gst/overrides/Makefile
 gst/plugins/Makefile
 gst/plugins/fault_injection/Makefile
 gst/plugins/gapplication/Makefile
+gst/plugins/gtk/Makefile
 tests/Makefile
 tests/check/Makefile
 pkgconfig/Makefile
index 0b7b25f..3329fcc 100644 (file)
@@ -1 +1,5 @@
 SUBDIRS = fault_injection gapplication
+
+if HAVE_GTK
+SUBDIRS += gtk
+endif
diff --git a/validate/gst/plugins/gtk/Makefile.am b/validate/gst/plugins/gtk/Makefile.am
new file mode 100644 (file)
index 0000000..f049a27
--- /dev/null
@@ -0,0 +1,11 @@
+plugin_LTLIBRARIES = libgstvalidategtk.la
+
+libgstvalidategtk_la_SOURCES = gstvalidategtk.c
+
+libgstvalidategtk_la_CFLAGS = $(GST_ALL_CFLAGS) $(GTK_CFLAGS)
+libgstvalidategtk_la_LIBADD = $(GST_ALL_LIBS) $(top_builddir)/gst/validate/libgstvalidate-@GST_API_VERSION@.la $(GTK_LIBS)
+libgstvalidategtk_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) $(GST_ALL_LDFLAGS) $(GTK_LDFLAGS)
+
+CLEANFILES =
+
+
diff --git a/validate/gst/plugins/gtk/gstvalidategtk.c b/validate/gst/plugins/gtk/gstvalidategtk.c
new file mode 100644 (file)
index 0000000..2154a7d
--- /dev/null
@@ -0,0 +1,519 @@
+/* GStreamer
+ *
+ * Copyright (C) 2015 Raspberry Pi Foundation
+ *  Author: Thibault Saunier <thibault.saunier@collabora.com>
+ *
+ * gstvalidategtk.c: GstValidateActionTypes to use with gtk applications
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#define _GNU_SOURCE
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <gst/gst.h>
+#include <gtk/gtk.h>
+#include <gdk/gdk.h>
+
+#include "../../validate/gst-validate-report.h"
+#include "../../validate/gst-validate-reporter.h"
+#include "../../validate/validate.h"
+#include "../../validate/gst-validate-scenario.h"
+#include "../../validate/gst-validate-utils.h"
+
+#define ACTION_GDKEVENTS_QUARK g_quark_from_static_string("ACTION_GDKEVENTS_QUARK")
+static GList *awaited_actions = NULL;   /* A list of GstValidateAction to be executed */
+
+static const gchar *
+get_widget_name (GtkWidget * widget)
+{
+  const gchar *name = NULL;
+
+  if (GTK_IS_BUILDABLE (widget))
+    name = gtk_buildable_get_name (GTK_BUILDABLE (widget));
+
+  if (!name) {
+    name = gtk_widget_get_name (widget);
+  }
+
+  return name;
+}
+
+static GdkEventType
+get_event_type (GstValidateScenario * scenario, GstValidateAction * action)
+{
+  guint type;
+  const gchar *etype_str = gst_structure_get_string (action->structure, "type");
+
+  if (!etype_str)
+    return GDK_NOTHING;
+
+  if (gst_validate_utils_enum_from_str (GDK_TYPE_EVENT_TYPE, etype_str, &type))
+    return type;
+
+  GST_VALIDATE_REPORT (scenario,
+      g_quark_from_static_string ("scenario::execution-error"),
+      "Uknown event type %s, the string should look like the ones defined in "
+      "gdk_event_type_get_type", etype_str);
+
+  return -2;
+}
+
+static GdkDevice *
+get_device (GstValidateAction * action, GdkInputSource input_source)
+{
+  GList *tmp, *devices;
+  GdkDevice *device = NULL;
+  GdkDeviceManager *dev_manager;
+
+  dev_manager = gdk_display_get_device_manager (gdk_display_get_default ());
+  devices =
+      gdk_device_manager_list_devices (dev_manager, GDK_DEVICE_TYPE_MASTER);
+
+  for (tmp = devices; tmp; tmp = tmp->next) {
+    if (gdk_device_get_source (tmp->data) == input_source) {
+      device = tmp->data;
+      break;
+    }
+  }
+
+  g_list_free (devices);
+
+  return device;
+}
+
+static GdkEvent *
+_create_key_event (GdkWindow * window, GdkEventType etype, guint keyval,
+    guint hw_keycode, guint state, GdkDevice * device)
+{
+  GdkEvent *event = gdk_event_new (etype);
+  GdkEventKey *kevent = (GdkEventKey *) event;
+
+  kevent->window = g_object_ref (window);
+  kevent->send_event = TRUE;
+  kevent->time = GDK_CURRENT_TIME;
+  kevent->keyval = keyval;
+  kevent->hardware_keycode = hw_keycode;
+  kevent->state = state;
+
+  gdk_event_set_device (event, device);
+
+  return event;
+}
+
+static GList *
+_create_keyboard_events (GstValidateAction * action,
+    GdkWindow * window, const gchar * keyname, const gchar * string,
+    GdkEventType etype)
+{
+  guint *keys;
+  GList *events = NULL;
+  GdkDevice *device = NULL;
+
+  if (etype == GDK_NOTHING) {
+    etype = GDK_KEY_PRESS;
+  } else if (etype != GDK_KEY_PRESS && etype != GDK_KEY_RELEASE) {
+    GST_VALIDATE_REPORT (action->scenario,
+        g_quark_from_static_string ("scenario::execution-error"),
+        "GdkEvent type %s does not work with the 'keys' parametter",
+        gst_structure_get_string (action->structure, "type"));
+
+    return NULL;
+  }
+
+
+  device = get_device (action, GDK_SOURCE_KEYBOARD);
+  if (device == NULL) {
+    GST_VALIDATE_REPORT (action->scenario,
+        g_quark_from_static_string ("scenario::execution-error"),
+        "Could not find a keyboard device");
+
+    return NULL;
+  }
+
+  if (keyname) {
+    guint keyval, state;
+
+    gtk_accelerator_parse_with_keycode (keyname, &keyval, &keys, &state);
+    events =
+        g_list_append (events, _create_key_event (window, etype, keyval,
+            keys[0], state, device));
+  } else if (string) {
+    gint i;
+
+    for (i = 0; string[i]; i++) {
+      gint n_keys;
+      GdkKeymapKey *kmaps;
+      guint keyval = gdk_unicode_to_keyval (string[i]);
+
+      gdk_keymap_get_entries_for_keyval (gdk_keymap_get_default (),
+          keyval, &kmaps, &n_keys);
+
+      events =
+          g_list_append (events, _create_key_event (window, etype, keyval,
+              kmaps[0].keycode, 0, device));
+    }
+  }
+
+  return events;
+}
+
+typedef struct
+{
+  gchar **widget_paths;
+  gint current_index;
+  GtkWidget *widget;
+  gboolean found;
+} WidgetNameWidget;
+
+static GtkWidget *_find_widget (GtkContainer * container,
+    WidgetNameWidget * res);
+
+static gboolean
+_widget_has_name (GtkWidget * widget, gchar * name)
+{
+  if (g_strcmp0 (get_widget_name (GTK_WIDGET (widget)), name) == 0) {
+    return TRUE;
+  }
+
+  return FALSE;
+}
+
+static void
+_find_widget_cb (GtkWidget * child, WidgetNameWidget * res)
+{
+  if (res->found) {
+    return;
+  }
+
+  if (_widget_has_name (child, res->widget_paths[res->current_index])) {
+    res->current_index++;
+
+    if (res->widget_paths[res->current_index] == NULL) {
+      res->widget = child;
+      res->found = TRUE;
+      GST_ERROR ("%p GOT IT!!! %s", child,
+          gtk_buildable_get_name (GTK_BUILDABLE (child)));
+    } else if (GTK_CONTAINER (child)) {
+      res->widget = _find_widget (GTK_CONTAINER (child), res);
+    }
+
+  } else {
+    if (GTK_IS_CONTAINER (child)) {
+      res->widget = _find_widget (GTK_CONTAINER (child), res);
+    }
+  }
+
+}
+
+static GtkWidget *
+_find_widget (GtkContainer * container, WidgetNameWidget * res)
+{
+  if (res->found)
+    return res->widget;
+
+  if (_widget_has_name (GTK_WIDGET (container),
+          res->widget_paths[res->current_index])) {
+    res->current_index++;
+
+    if (res->widget_paths[res->current_index] == NULL)
+      return GTK_WIDGET (container);
+  }
+
+  gtk_container_forall (container, (GtkCallback) _find_widget_cb, res);
+
+  if (res->widget) {
+    res->current_index++;
+
+    if (res->widget_paths[res->current_index + 1] == NULL)
+      return res->widget;
+
+    if (GTK_IS_CONTAINER (res->widget))
+      _find_widget (GTK_CONTAINER (res->widget), res);
+  }
+
+  return res->widget;
+}
+
+
+static void
+_find_button (GtkWidget * widget, GtkWidget ** button)
+{
+  if (GTK_IS_BUTTON (widget))
+    *button = widget;
+}
+
+/*  Copy pasted from gtk+/gtk/gtktestutils.c */
+static GSList *
+test_find_widget_input_windows (GtkWidget * widget, gboolean input_only)
+{
+  GdkWindow *window;
+  GList *node, *children;
+  GSList *matches = NULL;
+  gpointer udata;
+
+  window = gtk_widget_get_window (widget);
+
+  gdk_window_get_user_data (window, &udata);
+  if (udata == widget && (!input_only || (GDK_IS_WINDOW (window)
+              && gdk_window_is_input_only (GDK_WINDOW (window)))))
+    matches = g_slist_prepend (matches, window);
+  children = gdk_window_get_children (gtk_widget_get_parent_window (widget));
+  for (node = children; node; node = node->next) {
+    gdk_window_get_user_data (node->data, &udata);
+    if (udata == widget && (!input_only || (GDK_IS_WINDOW (node->data)
+                && gdk_window_is_input_only (GDK_WINDOW (node->data)))))
+      matches = g_slist_prepend (matches, node->data);
+  }
+  return g_slist_reverse (matches);
+}
+
+static GdkWindow *
+widget_get_window (GtkWidget * widget)
+{
+  GdkWindow *res = NULL;
+  GSList *iwindows = test_find_widget_input_windows (widget, FALSE);
+
+  if (!iwindows)
+    iwindows = test_find_widget_input_windows (widget, TRUE);
+
+  if (iwindows)
+    res = iwindows->data;
+
+  g_slist_free (iwindows);
+
+  return res;
+}
+
+static GdkWindow *
+get_window (GstValidateScenario * scenario, GstValidateAction * action,
+    const gchar * widget_name)
+{
+  GList *tmptoplevel;
+  GdkWindow *res = NULL;
+  gchar **widget_paths = NULL;
+
+  GList *toplevels = gtk_window_list_toplevels ();
+
+  if (!widget_name)
+    widget_name = gst_structure_get_string (action->structure, "widget-name");
+
+  if (!toplevels) {
+    GST_VALIDATE_REPORT (scenario,
+        g_quark_from_static_string ("scenario::execution-error"),
+        "No Gtk topelevel window found, can not sent GdkEvent");
+
+    return NULL;
+  }
+
+  if (!widget_name) {
+    res = gtk_widget_get_window (toplevels->data);
+
+    goto done;
+  }
+
+  widget_paths = g_strsplit (widget_name, "/", -1);
+
+  for (tmptoplevel = toplevels; tmptoplevel; tmptoplevel = tmptoplevel->next) {
+    GtkWidget *widget;
+    WidgetNameWidget wn;
+
+    wn.widget_paths = widget_paths;
+    wn.current_index = 0;
+    wn.found = FALSE;
+    wn.widget = NULL;
+
+    widget = _find_widget (tmptoplevel->data, &wn);
+    if (widget) {
+      if (GTK_IS_TOOL_BUTTON (widget)) {
+        GST_ERROR ("IS TOOL BUTTON");
+        gtk_container_forall (GTK_CONTAINER (widget),
+            (GtkCallback) _find_button, &widget);
+      }
+
+      res = widget_get_window (widget);
+      break;
+    }
+  }
+
+done:
+  g_list_free (toplevels);
+
+  return res;
+}
+
+static GstValidateActionReturn
+_put_events (GstValidateAction * action, GList * events)
+{
+  GList *tmp;
+
+  if (events == NULL)
+    return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
+
+  gst_mini_object_set_qdata (GST_MINI_OBJECT (action), ACTION_GDKEVENTS_QUARK,
+      events, NULL);
+  awaited_actions = g_list_append (awaited_actions, action);
+
+  for (tmp = events; tmp; tmp = tmp->next) {
+    gdk_event_put (tmp->data);
+  }
+
+  return GST_VALIDATE_EXECUTE_ACTION_ASYNC;
+}
+
+static gboolean
+_execute_put_events (GstValidateScenario * scenario, GstValidateAction * action)
+{
+  GdkEventType etype;
+  const gchar *keys, *string;
+
+  GList *events = NULL;
+  GdkWindow *window = get_window (scenario, action, NULL);
+
+  if (!window)
+    return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
+
+  etype = get_event_type (scenario, action);
+  if (etype == -2)
+    return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
+
+  keys = gst_structure_get_string (action->structure, "keys");
+  string = gst_structure_get_string (action->structure, "string");
+  if (keys || string) {
+    events = _create_keyboard_events (action, window, keys, string, etype);
+
+    return _put_events (action, events);
+  }
+
+  GST_VALIDATE_REPORT (scenario,
+      g_quark_from_static_string ("scenario::execution-error"),
+      "Action parametters not supported yet");
+
+  return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
+}
+
+static void
+_process_event (GdkEvent * event, gpointer data)
+{
+  GList *tmp;
+  GdkEvent *done_event = NULL;
+  GstValidateAction *action = NULL;
+
+  for (tmp = awaited_actions; tmp; tmp = tmp->next) {
+    GstValidateAction *tmp_action = tmp->data;
+    GdkEvent *awaited_event =
+        ((GList *) gst_mini_object_get_qdata (GST_MINI_OBJECT (tmp_action),
+            ACTION_GDKEVENTS_QUARK))->data;
+
+    if (gdk_event_get_event_type (awaited_event) ==
+        gdk_event_get_event_type (event)
+        && ((GdkEventAny *) event)->window ==
+        ((GdkEventAny *) awaited_event)->window) {
+
+      switch (gdk_event_get_event_type (awaited_event)) {
+        case GDK_KEY_PRESS:
+        case GDK_KEY_RELEASE:
+          if (event->key.keyval == awaited_event->key.keyval) {
+            done_event = awaited_event;
+            action = tmp_action;
+          }
+          break;
+        default:
+          g_assert_not_reached ();
+      }
+    }
+  }
+
+  if (done_event) {
+    GList *awaited_events = gst_mini_object_get_qdata (GST_MINI_OBJECT (action),
+        ACTION_GDKEVENTS_QUARK);
+
+    awaited_events = g_list_remove (awaited_events, done_event);
+    gdk_event_free (done_event);
+    gst_mini_object_set_qdata (GST_MINI_OBJECT (action), ACTION_GDKEVENTS_QUARK,
+        awaited_events, NULL);
+
+    if (awaited_events == NULL) {
+      awaited_actions = g_list_remove (awaited_actions, action);
+      gst_validate_action_set_done (action);
+    }
+  }
+
+  gtk_main_do_event (event);
+}
+
+static gboolean
+gst_validate_gtk_init (GstPlugin * plugin)
+{
+  gdk_event_handler_set (_process_event, NULL, NULL);
+
+/*  *INDENT-OFF* */
+  gst_validate_register_action_type_dynamic (plugin, "gtk-put-event",
+      GST_RANK_PRIMARY, _execute_put_events, ((GstValidateActionParameter[]) {
+            {
+              .name = "keys",
+              .description = "The keyboard keys to be used for the event, parsed"
+              " with gtk_accelerator_parse_with_keycode, so refer to its documentation"
+              " for more information",
+              .mandatory = FALSE,
+              .types = "string",
+              .possible_variables = NULL,
+            },
+            {
+              .name = "string",
+              .description = "The string to be 'written' by the keyboard"
+              " sending KEY_PRESS GdkEvents",
+              .mandatory = FALSE,
+              .types = "string",
+              .possible_variables = NULL,
+            },
+            {
+              .name = "type",
+              .description = "The event type to get executed. "
+              "the string should look like the ones in GdkEventType but without"
+              " the leading 'GDK_'. It is not mandatory as it can be computed from"
+              " other present fields (e.g, an action with 'keys' will concider the type"
+              " as 'key_pressed' by default).",
+              .mandatory = FALSE,
+              .types = "string",
+            },
+            {
+              .name = "widget-name",
+              .description = "The name of the target GdkWidget of the GdkEvent"
+                ". That widget has to contain a GdkWindow. If not specified,"
+                " the event will be sent to the first toplevel window",
+              .mandatory = FALSE,
+              .types = "string",
+              .possible_variables = NULL,
+            },
+            {NULL}
+          }),
+      "Put a GdkEvent on the event list using gdk_put_event",
+      GST_VALIDATE_ACTION_TYPE_NO_EXECUTION_NOT_FATAL);
+/*  *INDENT-ON* */
+
+  return TRUE;
+}
+
+GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
+    GST_VERSION_MINOR,
+    gstvalidategtk,
+    "GstValidate plugin to execute action specific to the Gtk toolkit",
+    gst_validate_gtk_init, VERSION, "LGPL", GST_PACKAGE_NAME,
+    GST_PACKAGE_ORIGIN)