9d5d0494348c21e85e9c71349d7576cfae2a4a68
[platform/upstream/gstreamer.git] / validate / plugins / gtk / gstvalidategtk.c
1 /* GStreamer
2  *
3  * Copyright (C) 2015 Raspberry Pi Foundation
4  *  Author: Thibault Saunier <thibault.saunier@collabora.com>
5  *
6  * gstvalidategtk.c: GstValidateActionTypes to use with gtk applications
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the
20  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21  * Boston, MA 02111-1307, USA.
22  */
23
24 #define _GNU_SOURCE
25
26 #ifdef HAVE_CONFIG_H
27 #include "config.h"
28 #endif
29
30 #include <gst/gst.h>
31 #include <gtk/gtk.h>
32 #include <gdk/gdk.h>
33
34 #include "../../gst/validate/gst-validate-report.h"
35 #include "../../gst/validate/gst-validate-reporter.h"
36 #include "../../gst/validate/validate.h"
37 #include "../../gst/validate/gst-validate-scenario.h"
38 #include "../../gst/validate/gst-validate-utils.h"
39
40 #define ACTION_GDKEVENTS_QUARK g_quark_from_static_string("ACTION_GDKEVENTS_QUARK")
41 static GList *awaited_actions = NULL;   /* A list of GstValidateAction to be executed */
42
43 static const gchar *
44 get_widget_name (GtkWidget * widget)
45 {
46   const gchar *name = NULL;
47
48   if (GTK_IS_BUILDABLE (widget))
49     name = gtk_buildable_get_name (GTK_BUILDABLE (widget));
50
51   if (!name) {
52     name = gtk_widget_get_name (widget);
53   }
54
55   return name;
56 }
57
58 static GdkEventType
59 get_event_type (GstValidateScenario * scenario, GstValidateAction * action)
60 {
61   guint type;
62   const gchar *etype_str = gst_structure_get_string (action->structure, "type");
63
64   if (!etype_str)
65     return GDK_NOTHING;
66
67   if (gst_validate_utils_enum_from_str (GDK_TYPE_EVENT_TYPE, etype_str, &type))
68     return type;
69
70   GST_VALIDATE_REPORT (scenario,
71       g_quark_from_static_string ("scenario::execution-error"),
72       "Uknown event type %s, the string should look like the ones defined in "
73       "gdk_event_type_get_type", etype_str);
74
75   return -2;
76 }
77
78 #if ! GTK_CHECK_VERSION(3,20,0)
79 static GdkDevice *
80 get_device (GstValidateAction * action, GdkInputSource input_source)
81 {
82   GList *tmp, *devices;
83   GdkDevice *device = NULL;
84   GdkDeviceManager *dev_manager;
85
86   dev_manager = gdk_display_get_device_manager (gdk_display_get_default ());
87   devices =
88       gdk_device_manager_list_devices (dev_manager, GDK_DEVICE_TYPE_MASTER);
89
90   for (tmp = devices; tmp; tmp = tmp->next) {
91     if (gdk_device_get_source (tmp->data) == input_source) {
92       device = tmp->data;
93       break;
94     }
95   }
96
97   g_list_free (devices);
98
99   return device;
100 }
101 #endif
102
103 static GdkEvent *
104 _create_key_event (GdkWindow * window, GdkEventType etype, guint keyval,
105     guint hw_keycode, guint state, GdkDevice * device)
106 {
107   GdkEvent *event = gdk_event_new (etype);
108   GdkEventKey *kevent = (GdkEventKey *) event;
109
110   kevent->window = g_object_ref (window);
111   kevent->send_event = TRUE;
112   kevent->time = GDK_CURRENT_TIME;
113   kevent->keyval = keyval;
114   kevent->hardware_keycode = hw_keycode;
115   kevent->state = state;
116
117   gdk_event_set_device (event, device);
118
119   return event;
120 }
121
122 static GList *
123 _create_keyboard_events (GstValidateAction * action,
124     GdkWindow * window, const gchar * keyname, const gchar * string,
125     GdkEventType etype)
126 {
127   guint *keys;
128 #if GTK_CHECK_VERSION(3,20,0)
129   GdkDisplay *display;
130   GdkSeat *seat;
131 #endif
132   GList *events = NULL;
133   GdkDevice *device = NULL;
134   GstValidateScenario *scenario = gst_validate_action_get_scenario (action);
135
136   if (etype == GDK_NOTHING) {
137     etype = GDK_KEY_PRESS;
138   } else if (etype != GDK_KEY_PRESS && etype != GDK_KEY_RELEASE) {
139     GST_VALIDATE_REPORT (scenario,
140         g_quark_from_static_string ("scenario::execution-error"),
141         "GdkEvent type %s does not work with the 'keys' parameter",
142         gst_structure_get_string (action->structure, "type"));
143
144     goto fail;
145   }
146 #if GTK_CHECK_VERSION(3,20,0)
147   display = gdk_display_get_default ();
148   if (display == NULL) {
149     GST_VALIDATE_REPORT (scenario,
150         g_quark_from_static_string ("scenario::execution-error"),
151         "Could not find a display");
152
153     goto fail;
154   }
155
156   seat = gdk_display_get_default_seat (display);
157   device = gdk_seat_get_keyboard (seat);
158 #else
159   device = get_device (action, GDK_SOURCE_KEYBOARD);
160 #endif
161   if (device == NULL) {
162     GST_VALIDATE_REPORT (scenario,
163         g_quark_from_static_string ("scenario::execution-error"),
164         "Could not find a keyboard device");
165
166     goto fail;
167   }
168
169   if (keyname) {
170     guint keyval, state;
171
172     gtk_accelerator_parse_with_keycode (keyname, &keyval, &keys, &state);
173     events =
174         g_list_append (events, _create_key_event (window, etype, keyval,
175             keys ? keys[0] : 0, state, device));
176   } else if (string) {
177     gint i;
178
179     for (i = 0; string[i]; i++) {
180       gint n_keys;
181       GdkKeymapKey *kmaps;
182       guint keyval = gdk_unicode_to_keyval (string[i]);
183
184       gdk_keymap_get_entries_for_keyval (gdk_keymap_get_for_display
185           (gdk_display_get_default ()), keyval, &kmaps, &n_keys);
186
187       events =
188           g_list_append (events, _create_key_event (window, etype, keyval,
189               kmaps[0].keycode, 0, device));
190     }
191   }
192
193   gst_object_unref (scenario);
194   return events;
195
196 fail:
197   gst_object_unref (scenario);
198
199   return NULL;
200 }
201
202 typedef struct
203 {
204   gchar **widget_paths;
205   gint current_index;
206   GtkWidget *widget;
207   gboolean found;
208 } WidgetNameWidget;
209
210 static GtkWidget *_find_widget (GtkContainer * container,
211     WidgetNameWidget * res);
212
213 static gboolean
214 _widget_has_name (GtkWidget * widget, gchar * name)
215 {
216   if (g_strcmp0 (get_widget_name (GTK_WIDGET (widget)), name) == 0) {
217     return TRUE;
218   }
219
220   return FALSE;
221 }
222
223 static void
224 _find_widget_cb (GtkWidget * child, WidgetNameWidget * res)
225 {
226   if (res->found) {
227     return;
228   }
229
230   if (_widget_has_name (child, res->widget_paths[res->current_index])) {
231     res->current_index++;
232
233     if (res->widget_paths[res->current_index] == NULL) {
234       res->widget = child;
235       res->found = TRUE;
236       GST_ERROR ("%p GOT IT!!! %s", child,
237           gtk_buildable_get_name (GTK_BUILDABLE (child)));
238     } else if (GTK_CONTAINER (child)) {
239       res->widget = _find_widget (GTK_CONTAINER (child), res);
240     }
241
242   } else {
243     if (GTK_IS_CONTAINER (child)) {
244       res->widget = _find_widget (GTK_CONTAINER (child), res);
245     }
246   }
247
248 }
249
250 static GtkWidget *
251 _find_widget (GtkContainer * container, WidgetNameWidget * res)
252 {
253   if (res->found)
254     return res->widget;
255
256   if (_widget_has_name (GTK_WIDGET (container),
257           res->widget_paths[res->current_index])) {
258     res->current_index++;
259
260     if (res->widget_paths[res->current_index] == NULL)
261       return GTK_WIDGET (container);
262   }
263
264   gtk_container_forall (container, (GtkCallback) _find_widget_cb, res);
265
266   if (res->widget) {
267     res->current_index++;
268
269     if (res->widget_paths[res->current_index + 1] == NULL)
270       return res->widget;
271
272     if (GTK_IS_CONTAINER (res->widget))
273       _find_widget (GTK_CONTAINER (res->widget), res);
274   }
275
276   return res->widget;
277 }
278
279
280 static void
281 _find_button (GtkWidget * widget, GtkWidget ** button)
282 {
283   if (GTK_IS_BUTTON (widget))
284     *button = widget;
285 }
286
287 /*  Copy pasted from gtk+/gtk/gtktestutils.c */
288 static GSList *
289 test_find_widget_input_windows (GtkWidget * widget, gboolean input_only)
290 {
291   GdkWindow *window;
292   GList *node, *children;
293   GSList *matches = NULL;
294   gpointer udata;
295
296   window = gtk_widget_get_window (widget);
297
298   gdk_window_get_user_data (window, &udata);
299   if (udata == widget && (!input_only || (GDK_IS_WINDOW (window)
300               && gdk_window_is_input_only (GDK_WINDOW (window)))))
301     matches = g_slist_prepend (matches, window);
302   children = gdk_window_get_children (gtk_widget_get_parent_window (widget));
303   for (node = children; node; node = node->next) {
304     gdk_window_get_user_data (node->data, &udata);
305     if (udata == widget && (!input_only || (GDK_IS_WINDOW (node->data)
306                 && gdk_window_is_input_only (GDK_WINDOW (node->data)))))
307       matches = g_slist_prepend (matches, node->data);
308   }
309   return g_slist_reverse (matches);
310 }
311
312 static GdkWindow *
313 widget_get_window (GtkWidget * widget)
314 {
315   GdkWindow *res = NULL;
316   GSList *iwindows = test_find_widget_input_windows (widget, FALSE);
317
318   if (!iwindows)
319     iwindows = test_find_widget_input_windows (widget, TRUE);
320
321   if (iwindows)
322     res = iwindows->data;
323
324   g_slist_free (iwindows);
325
326   return res;
327 }
328
329 static GdkWindow *
330 get_window (GstValidateScenario * scenario, GstValidateAction * action,
331     const gchar * widget_name)
332 {
333   GList *tmptoplevel;
334   GdkWindow *res = NULL;
335   gchar **widget_paths = NULL;
336
337   GList *toplevels = gtk_window_list_toplevels ();
338
339   if (!widget_name)
340     widget_name = gst_structure_get_string (action->structure, "widget-name");
341
342   if (!toplevels) {
343     GST_VALIDATE_REPORT (scenario,
344         g_quark_from_static_string ("scenario::execution-error"),
345         "No Gtk topelevel window found, can not sent GdkEvent");
346
347     return NULL;
348   }
349
350   if (!widget_name) {
351     res = gtk_widget_get_window (toplevels->data);
352
353     goto done;
354   }
355
356   widget_paths = g_strsplit (widget_name, "/", -1);
357
358   for (tmptoplevel = toplevels; tmptoplevel; tmptoplevel = tmptoplevel->next) {
359     GtkWidget *widget;
360     WidgetNameWidget wn;
361
362     wn.widget_paths = widget_paths;
363     wn.current_index = 0;
364     wn.found = FALSE;
365     wn.widget = NULL;
366
367     widget = _find_widget (tmptoplevel->data, &wn);
368     if (widget) {
369       if (GTK_IS_TOOL_BUTTON (widget)) {
370         GST_ERROR ("IS TOOL BUTTON");
371         gtk_container_forall (GTK_CONTAINER (widget),
372             (GtkCallback) _find_button, &widget);
373       }
374
375       res = widget_get_window (widget);
376       break;
377     }
378   }
379
380 done:
381   g_list_free (toplevels);
382
383   return res;
384 }
385
386 static GstValidateActionReturn
387 _put_events (GstValidateAction * action, GList * events)
388 {
389   GList *tmp;
390
391   if (events == NULL)
392     return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
393
394   gst_mini_object_set_qdata (GST_MINI_OBJECT (action), ACTION_GDKEVENTS_QUARK,
395       events, NULL);
396   awaited_actions = g_list_append (awaited_actions, action);
397
398   for (tmp = events; tmp; tmp = tmp->next) {
399     gdk_event_put (tmp->data);
400   }
401
402   return GST_VALIDATE_EXECUTE_ACTION_ASYNC;
403 }
404
405 static gboolean
406 _execute_put_events (GstValidateScenario * scenario, GstValidateAction * action)
407 {
408   GdkEventType etype;
409   const gchar *keys, *string;
410
411   GList *events = NULL;
412   GdkWindow *window = get_window (scenario, action, NULL);
413
414   if (!window)
415     return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
416
417   etype = get_event_type (scenario, action);
418   if (etype == -2)
419     return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
420
421   keys = gst_structure_get_string (action->structure, "keys");
422   string = gst_structure_get_string (action->structure, "string");
423   if (keys || string) {
424     events = _create_keyboard_events (action, window, keys, string, etype);
425
426     return _put_events (action, events);
427   }
428
429   GST_VALIDATE_REPORT (scenario,
430       g_quark_from_static_string ("scenario::execution-error"),
431       "Action parameters not supported yet");
432
433   return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
434 }
435
436 static void
437 _process_event (GdkEvent * event, gpointer data)
438 {
439   GList *tmp;
440   GdkEvent *done_event = NULL;
441   GstValidateAction *action = NULL;
442
443   for (tmp = awaited_actions; tmp; tmp = tmp->next) {
444     GstValidateAction *tmp_action = tmp->data;
445     GdkEvent *awaited_event =
446         ((GList *) gst_mini_object_get_qdata (GST_MINI_OBJECT (tmp_action),
447             ACTION_GDKEVENTS_QUARK))->data;
448
449     if (awaited_event->type == event->type
450         && ((GdkEventAny *) event)->window ==
451         ((GdkEventAny *) awaited_event)->window) {
452
453       switch (awaited_event->type) {
454         case GDK_KEY_PRESS:
455         case GDK_KEY_RELEASE:
456           if (event->key.keyval == awaited_event->key.keyval) {
457             done_event = awaited_event;
458             action = tmp_action;
459           }
460           break;
461         default:
462           g_assert_not_reached ();
463       }
464     }
465   }
466
467   if (done_event) {
468     GList *awaited_events = gst_mini_object_get_qdata (GST_MINI_OBJECT (action),
469         ACTION_GDKEVENTS_QUARK);
470
471     awaited_events = g_list_remove (awaited_events, done_event);
472     gdk_event_free (done_event);
473     gst_mini_object_set_qdata (GST_MINI_OBJECT (action), ACTION_GDKEVENTS_QUARK,
474         awaited_events, NULL);
475
476     if (awaited_events == NULL) {
477       awaited_actions = g_list_remove (awaited_actions, action);
478       gst_validate_action_set_done (action);
479     }
480   }
481
482   gtk_main_do_event (event);
483 }
484
485 static gboolean
486 gst_validate_gtk_init (GstPlugin * plugin)
487 {
488   gdk_event_handler_set (_process_event, NULL, NULL);
489
490 /*  *INDENT-OFF* */
491   gst_validate_register_action_type_dynamic (plugin, "gtk-put-event",
492       GST_RANK_PRIMARY, _execute_put_events, ((GstValidateActionParameter[]) {
493             {
494               .name = "keys",
495               .description = "The keyboard keys to be used for the event, parsed"
496               " with gtk_accelerator_parse_with_keycode, so refer to its documentation"
497               " for more information",
498               .mandatory = FALSE,
499               .types = "string",
500               .possible_variables = NULL,
501             },
502             {
503               .name = "string",
504               .description = "The string to be 'written' by the keyboard"
505               " sending KEY_PRESS GdkEvents",
506               .mandatory = FALSE,
507               .types = "string",
508               .possible_variables = NULL,
509             },
510             {
511               .name = "type",
512               .description = "The event type to get executed. "
513               "the string should look like the ones in GdkEventType but without"
514               " the leading 'GDK_'. It is not mandatory as it can be computed from"
515               " other present fields (e.g, an action with 'keys' will consider the type"
516               " as 'key_pressed' by default).",
517               .mandatory = FALSE,
518               .types = "string",
519             },
520             {
521               .name = "widget-name",
522               .description = "The name of the target GdkWidget of the GdkEvent"
523                 ". That widget has to contain a GdkWindow. If not specified,"
524                 " the event will be sent to the first toplevel window",
525               .mandatory = FALSE,
526               .types = "string",
527               .possible_variables = NULL,
528             },
529             {NULL}
530           }),
531       "Put a GdkEvent on the event list using gdk_put_event",
532       GST_VALIDATE_ACTION_TYPE_NO_EXECUTION_NOT_FATAL |
533       GST_VALIDATE_ACTION_TYPE_DOESNT_NEED_PIPELINE);
534 /*  *INDENT-ON* */
535
536   return TRUE;
537 }
538
539 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
540     GST_VERSION_MINOR,
541     validategtk,
542     "GstValidate plugin to execute action specific to the Gtk toolkit",
543     gst_validate_gtk_init, VERSION, "LGPL", GST_PACKAGE_NAME,
544     GST_PACKAGE_ORIGIN)