gstreamer/validate/plugins/gtk/gstvalidategtk.c
Thibault Saunier 8f123cbc6f validate: Make accessing Scenario.pipeline thread safe
The fact that Scenario.pipeline was not accessible in a thread way lead
to the fact that all users had to take the unref the last pipeline ref
in the main thread, otherwise we were crying. This was an ugly
restriction which lead to issue when using scenario on gst-rtsp-server.

This break the API as this commit remove the GstValidateScenario.pipeline
field but it is worth it.
2017-05-31 14:53:05 -04:00

544 lines
15 KiB
C

/* 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 "../../gst/validate/gst-validate-report.h"
#include "../../gst/validate/gst-validate-reporter.h"
#include "../../gst/validate/validate.h"
#include "../../gst/validate/gst-validate-scenario.h"
#include "../../gst/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;
}
#if ! GTK_CHECK_VERSION(3,20,0)
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;
}
#endif
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;
#if GTK_CHECK_VERSION(3,20,0)
GdkDisplay *display;
GdkSeat *seat;
#endif
GList *events = NULL;
GdkDevice *device = NULL;
GstValidateScenario *scenario = gst_validate_action_get_scenario (action);
if (etype == GDK_NOTHING) {
etype = GDK_KEY_PRESS;
} else if (etype != GDK_KEY_PRESS && etype != GDK_KEY_RELEASE) {
GST_VALIDATE_REPORT (scenario,
g_quark_from_static_string ("scenario::execution-error"),
"GdkEvent type %s does not work with the 'keys' parameter",
gst_structure_get_string (action->structure, "type"));
goto fail;
}
#if GTK_CHECK_VERSION(3,20,0)
display = gdk_display_get_default ();
if (display == NULL) {
GST_VALIDATE_REPORT (scenario,
g_quark_from_static_string ("scenario::execution-error"),
"Could not find a display");
goto fail;
}
seat = gdk_display_get_default_seat (display);
device = gdk_seat_get_keyboard (seat);
#else
device = get_device (action, GDK_SOURCE_KEYBOARD);
#endif
if (device == NULL) {
GST_VALIDATE_REPORT (scenario,
g_quark_from_static_string ("scenario::execution-error"),
"Could not find a keyboard device");
goto fail;
}
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 ? keys[0] : 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));
}
}
gst_object_unref (scenario);
return events;
fail:
gst_object_unref (scenario);
return NULL;
}
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 parameters 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 (awaited_event->type == event->type
&& ((GdkEventAny *) event)->window ==
((GdkEventAny *) awaited_event)->window) {
switch (awaited_event->type) {
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 consider 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 |
GST_VALIDATE_ACTION_TYPE_DOESNT_NEED_PIPELINE);
/* *INDENT-ON* */
return TRUE;
}
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
GST_VERSION_MINOR,
validategtk,
"GstValidate plugin to execute action specific to the Gtk toolkit",
gst_validate_gtk_init, VERSION, "LGPL", GST_PACKAGE_NAME,
GST_PACKAGE_ORIGIN)