1 /*
2  * GStreamer
3  * Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
4  * Copyright (C) 2020 Rafał Dzięgiel <rafostar.github@gmail.com>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  */
21 
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25 
26 #include <stdio.h>
27 
28 #include "gtkgstbasewidget.h"
29 
30 GST_DEBUG_CATEGORY (gst_debug_gtk_base_widget);
31 #define GST_CAT_DEFAULT gst_debug_gtk_base_widget
32 
33 #define DEFAULT_FORCE_ASPECT_RATIO  TRUE
34 #define DEFAULT_PAR_N               0
35 #define DEFAULT_PAR_D               1
36 #define DEFAULT_IGNORE_ALPHA        TRUE
37 
38 enum
39 {
40   PROP_0,
41   PROP_FORCE_ASPECT_RATIO,
42   PROP_PIXEL_ASPECT_RATIO,
43   PROP_IGNORE_ALPHA,
44 };
45 
46 static void
gtk_gst_base_widget_get_preferred_width(GtkWidget * widget,gint * min,gint * natural)47 gtk_gst_base_widget_get_preferred_width (GtkWidget * widget, gint * min,
48     gint * natural)
49 {
50   GtkGstBaseWidget *gst_widget = (GtkGstBaseWidget *) widget;
51   gint video_width = gst_widget->display_width;
52 
53   if (!gst_widget->negotiated)
54     video_width = 10;
55 
56   if (min)
57     *min = 1;
58   if (natural)
59     *natural = video_width;
60 }
61 
62 static void
gtk_gst_base_widget_get_preferred_height(GtkWidget * widget,gint * min,gint * natural)63 gtk_gst_base_widget_get_preferred_height (GtkWidget * widget, gint * min,
64     gint * natural)
65 {
66   GtkGstBaseWidget *gst_widget = (GtkGstBaseWidget *) widget;
67   gint video_height = gst_widget->display_height;
68 
69   if (!gst_widget->negotiated)
70     video_height = 10;
71 
72   if (min)
73     *min = 1;
74   if (natural)
75     *natural = video_height;
76 }
77 
78 #if defined(BUILD_FOR_GTK4)
79 static void
gtk_gst_base_widget_measure(GtkWidget * widget,GtkOrientation orientation,gint for_size,gint * min,gint * natural,gint * minimum_baseline,gint * natural_baseline)80 gtk_gst_base_widget_measure (GtkWidget * widget, GtkOrientation orientation,
81     gint for_size, gint * min, gint * natural,
82     gint * minimum_baseline, gint * natural_baseline)
83 {
84   if (orientation == GTK_ORIENTATION_HORIZONTAL)
85     gtk_gst_base_widget_get_preferred_width (widget, min, natural);
86   else
87     gtk_gst_base_widget_get_preferred_height (widget, min, natural);
88 
89   *minimum_baseline = -1;
90   *natural_baseline = -1;
91 }
92 #endif
93 
94 static void
gtk_gst_base_widget_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)95 gtk_gst_base_widget_set_property (GObject * object, guint prop_id,
96     const GValue * value, GParamSpec * pspec)
97 {
98   GtkGstBaseWidget *gtk_widget = GTK_GST_BASE_WIDGET (object);
99 
100   switch (prop_id) {
101     case PROP_FORCE_ASPECT_RATIO:
102       gtk_widget->force_aspect_ratio = g_value_get_boolean (value);
103       break;
104     case PROP_PIXEL_ASPECT_RATIO:
105       gtk_widget->par_n = gst_value_get_fraction_numerator (value);
106       gtk_widget->par_d = gst_value_get_fraction_denominator (value);
107       break;
108     case PROP_IGNORE_ALPHA:
109       gtk_widget->ignore_alpha = g_value_get_boolean (value);
110       break;
111     default:
112       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
113       break;
114   }
115 }
116 
117 static void
gtk_gst_base_widget_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)118 gtk_gst_base_widget_get_property (GObject * object, guint prop_id,
119     GValue * value, GParamSpec * pspec)
120 {
121   GtkGstBaseWidget *gtk_widget = GTK_GST_BASE_WIDGET (object);
122 
123   switch (prop_id) {
124     case PROP_FORCE_ASPECT_RATIO:
125       g_value_set_boolean (value, gtk_widget->force_aspect_ratio);
126       break;
127     case PROP_PIXEL_ASPECT_RATIO:
128       gst_value_set_fraction (value, gtk_widget->par_n, gtk_widget->par_d);
129       break;
130     case PROP_IGNORE_ALPHA:
131       g_value_set_boolean (value, gtk_widget->ignore_alpha);
132       break;
133     default:
134       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
135       break;
136   }
137 }
138 
139 static gboolean
_calculate_par(GtkGstBaseWidget * widget,GstVideoInfo * info)140 _calculate_par (GtkGstBaseWidget * widget, GstVideoInfo * info)
141 {
142   gboolean ok;
143   gint width, height;
144   gint par_n, par_d;
145   gint display_par_n, display_par_d;
146 
147   width = GST_VIDEO_INFO_WIDTH (info);
148   height = GST_VIDEO_INFO_HEIGHT (info);
149 
150   par_n = GST_VIDEO_INFO_PAR_N (info);
151   par_d = GST_VIDEO_INFO_PAR_D (info);
152 
153   if (!par_n)
154     par_n = 1;
155 
156   /* get display's PAR */
157   if (widget->par_n != 0 && widget->par_d != 0) {
158     display_par_n = widget->par_n;
159     display_par_d = widget->par_d;
160   } else {
161     display_par_n = 1;
162     display_par_d = 1;
163   }
164 
165 
166   ok = gst_video_calculate_display_ratio (&widget->display_ratio_num,
167       &widget->display_ratio_den, width, height, par_n, par_d, display_par_n,
168       display_par_d);
169 
170   if (ok) {
171     GST_LOG ("PAR: %u/%u DAR:%u/%u", par_n, par_d, display_par_n,
172         display_par_d);
173     return TRUE;
174   }
175 
176   return FALSE;
177 }
178 
179 static void
_apply_par(GtkGstBaseWidget * widget)180 _apply_par (GtkGstBaseWidget * widget)
181 {
182   guint display_ratio_num, display_ratio_den;
183   gint width, height;
184 
185   width = GST_VIDEO_INFO_WIDTH (&widget->v_info);
186   height = GST_VIDEO_INFO_HEIGHT (&widget->v_info);
187 
188   display_ratio_num = widget->display_ratio_num;
189   display_ratio_den = widget->display_ratio_den;
190 
191   if (height % display_ratio_den == 0) {
192     GST_DEBUG ("keeping video height");
193     widget->display_width = (guint)
194         gst_util_uint64_scale_int (height, display_ratio_num,
195         display_ratio_den);
196     widget->display_height = height;
197   } else if (width % display_ratio_num == 0) {
198     GST_DEBUG ("keeping video width");
199     widget->display_width = width;
200     widget->display_height = (guint)
201         gst_util_uint64_scale_int (width, display_ratio_den, display_ratio_num);
202   } else {
203     GST_DEBUG ("approximating while keeping video height");
204     widget->display_width = (guint)
205         gst_util_uint64_scale_int (height, display_ratio_num,
206         display_ratio_den);
207     widget->display_height = height;
208   }
209 
210   GST_DEBUG ("scaling to %dx%d", widget->display_width, widget->display_height);
211 }
212 
213 static gboolean
_queue_draw(GtkGstBaseWidget * widget)214 _queue_draw (GtkGstBaseWidget * widget)
215 {
216   GTK_GST_BASE_WIDGET_LOCK (widget);
217   widget->draw_id = 0;
218 
219   if (widget->pending_resize) {
220     widget->pending_resize = FALSE;
221 
222     widget->v_info = widget->pending_v_info;
223     widget->negotiated = TRUE;
224 
225     _apply_par (widget);
226 
227     gtk_widget_queue_resize (GTK_WIDGET (widget));
228   } else {
229     gtk_widget_queue_draw (GTK_WIDGET (widget));
230   }
231 
232   GTK_GST_BASE_WIDGET_UNLOCK (widget);
233 
234   return G_SOURCE_REMOVE;
235 }
236 
237 static const gchar *
_gdk_key_to_navigation_string(guint keyval)238 _gdk_key_to_navigation_string (guint keyval)
239 {
240   /* TODO: expand */
241   switch (keyval) {
242 #define KEY(key) case GDK_KEY_ ## key: return G_STRINGIFY(key)
243       KEY (Up);
244       KEY (Down);
245       KEY (Left);
246       KEY (Right);
247       KEY (Home);
248       KEY (End);
249 #undef KEY
250     default:
251       return NULL;
252   }
253 }
254 
255 static GdkEvent *
_get_current_event(GtkEventController * controller)256 _get_current_event (GtkEventController * controller)
257 {
258 #if defined(BUILD_FOR_GTK4)
259   return gtk_event_controller_get_current_event (controller);
260 #else
261   return gtk_get_current_event ();
262 #endif
263 }
264 
265 static void
_gdk_event_free(GdkEvent * event)266 _gdk_event_free (GdkEvent * event)
267 {
268 #if !defined(BUILD_FOR_GTK4)
269   if (event)
270     gdk_event_free (event);
271 #endif
272 }
273 
274 static gboolean
gtk_gst_base_widget_key_event(GtkEventControllerKey * key_controller,guint keyval,guint keycode,GdkModifierType state)275 gtk_gst_base_widget_key_event (GtkEventControllerKey * key_controller,
276     guint keyval, guint keycode, GdkModifierType state)
277 {
278   GtkEventController *controller = GTK_EVENT_CONTROLLER (key_controller);
279   GtkWidget *widget = gtk_event_controller_get_widget (controller);
280   GtkGstBaseWidget *base_widget = GTK_GST_BASE_WIDGET (widget);
281   GstElement *element;
282 
283   if ((element = g_weak_ref_get (&base_widget->element))) {
284     if (GST_IS_NAVIGATION (element)) {
285       GdkEvent *event = _get_current_event (controller);
286       const gchar *str = _gdk_key_to_navigation_string (keyval);
287 
288       if (str) {
289         const gchar *key_type =
290             gdk_event_get_event_type (event) ==
291             GDK_KEY_PRESS ? "key-press" : "key-release";
292         gst_navigation_send_key_event (GST_NAVIGATION (element), key_type, str);
293       }
294       _gdk_event_free (event);
295     }
296     g_object_unref (element);
297   }
298 
299   return FALSE;
300 }
301 
302 static void
_fit_stream_to_allocated_size(GtkGstBaseWidget * base_widget,GtkAllocation * allocation,GstVideoRectangle * result)303 _fit_stream_to_allocated_size (GtkGstBaseWidget * base_widget,
304     GtkAllocation * allocation, GstVideoRectangle * result)
305 {
306   if (base_widget->force_aspect_ratio) {
307     GstVideoRectangle src, dst;
308 
309     src.x = 0;
310     src.y = 0;
311     src.w = base_widget->display_width;
312     src.h = base_widget->display_height;
313 
314     dst.x = 0;
315     dst.y = 0;
316     dst.w = allocation->width;
317     dst.h = allocation->height;
318 
319     gst_video_sink_center_rect (src, dst, result, TRUE);
320   } else {
321     result->x = 0;
322     result->y = 0;
323     result->w = allocation->width;
324     result->h = allocation->height;
325   }
326 }
327 
328 static void
_display_size_to_stream_size(GtkGstBaseWidget * base_widget,gdouble x,gdouble y,gdouble * stream_x,gdouble * stream_y)329 _display_size_to_stream_size (GtkGstBaseWidget * base_widget, gdouble x,
330     gdouble y, gdouble * stream_x, gdouble * stream_y)
331 {
332   gdouble stream_width, stream_height;
333   GtkAllocation allocation;
334   GstVideoRectangle result;
335 
336   gtk_widget_get_allocation (GTK_WIDGET (base_widget), &allocation);
337   _fit_stream_to_allocated_size (base_widget, &allocation, &result);
338 
339   stream_width = (gdouble) GST_VIDEO_INFO_WIDTH (&base_widget->v_info);
340   stream_height = (gdouble) GST_VIDEO_INFO_HEIGHT (&base_widget->v_info);
341 
342   /* from display coordinates to stream coordinates */
343   if (result.w > 0)
344     *stream_x = (x - result.x) / result.w * stream_width;
345   else
346     *stream_x = 0.;
347 
348   /* clip to stream size */
349   if (*stream_x < 0.)
350     *stream_x = 0.;
351   if (*stream_x > GST_VIDEO_INFO_WIDTH (&base_widget->v_info))
352     *stream_x = GST_VIDEO_INFO_WIDTH (&base_widget->v_info);
353 
354   /* same for y-axis */
355   if (result.h > 0)
356     *stream_y = (y - result.y) / result.h * stream_height;
357   else
358     *stream_y = 0.;
359 
360   if (*stream_y < 0.)
361     *stream_y = 0.;
362   if (*stream_y > GST_VIDEO_INFO_HEIGHT (&base_widget->v_info))
363     *stream_y = GST_VIDEO_INFO_HEIGHT (&base_widget->v_info);
364 
365   GST_TRACE ("transform %fx%f into %fx%f", x, y, *stream_x, *stream_y);
366 }
367 
368 static gboolean
gtk_gst_base_widget_button_event(GtkGestureClick * gesture,gint n_press,gdouble x,gdouble y)369 gtk_gst_base_widget_button_event (
370 #if defined(BUILD_FOR_GTK4)
371     GtkGestureClick * gesture,
372 #else
373     GtkGestureMultiPress * gesture,
374 #endif
375     gint n_press, gdouble x, gdouble y)
376 {
377   GtkEventController *controller = GTK_EVENT_CONTROLLER (gesture);
378   GtkWidget *widget = gtk_event_controller_get_widget (controller);
379   GtkGstBaseWidget *base_widget = GTK_GST_BASE_WIDGET (widget);
380   GstElement *element;
381 
382   if ((element = g_weak_ref_get (&base_widget->element))) {
383     if (GST_IS_NAVIGATION (element)) {
384       GdkEvent *event = _get_current_event (controller);
385       const gchar *key_type =
386           gdk_event_get_event_type (event) == GDK_BUTTON_PRESS
387           ? "mouse-button-press" : "mouse-button-release";
388       gdouble stream_x, stream_y;
389 #if !defined(BUILD_FOR_GTK4)
390       guint button;
391       gdk_event_get_button (event, &button);
392 #endif
393 
394       _display_size_to_stream_size (base_widget, x, y, &stream_x, &stream_y);
395 
396       gst_navigation_send_mouse_event (GST_NAVIGATION (element), key_type,
397 #if defined(BUILD_FOR_GTK4)
398           /* Gesture is set to ignore other buttons so we do not have to check */
399           GDK_BUTTON_PRIMARY,
400 #else
401           button,
402 #endif
403           stream_x, stream_y);
404 
405       _gdk_event_free (event);
406     }
407     g_object_unref (element);
408   }
409 
410   return FALSE;
411 }
412 
413 static gboolean
gtk_gst_base_widget_motion_event(GtkEventControllerMotion * motion_controller,gdouble x,gdouble y)414 gtk_gst_base_widget_motion_event (GtkEventControllerMotion * motion_controller,
415     gdouble x, gdouble y)
416 {
417   GtkEventController *controller = GTK_EVENT_CONTROLLER (motion_controller);
418   GtkWidget *widget = gtk_event_controller_get_widget (controller);
419   GtkGstBaseWidget *base_widget = GTK_GST_BASE_WIDGET (widget);
420   GstElement *element;
421 
422   if ((element = g_weak_ref_get (&base_widget->element))) {
423     if (GST_IS_NAVIGATION (element)) {
424       gdouble stream_x, stream_y;
425 
426       _display_size_to_stream_size (base_widget, x, y, &stream_x, &stream_y);
427 
428       gst_navigation_send_mouse_event (GST_NAVIGATION (element), "mouse-move",
429           0, stream_x, stream_y);
430     }
431     g_object_unref (element);
432   }
433 
434   return FALSE;
435 }
436 
437 void
gtk_gst_base_widget_class_init(GtkGstBaseWidgetClass * klass)438 gtk_gst_base_widget_class_init (GtkGstBaseWidgetClass * klass)
439 {
440   GObjectClass *gobject_klass = (GObjectClass *) klass;
441   GtkWidgetClass *widget_klass = (GtkWidgetClass *) klass;
442 
443   gobject_klass->set_property = gtk_gst_base_widget_set_property;
444   gobject_klass->get_property = gtk_gst_base_widget_get_property;
445 
446   g_object_class_install_property (gobject_klass, PROP_FORCE_ASPECT_RATIO,
447       g_param_spec_boolean ("force-aspect-ratio",
448           "Force aspect ratio",
449           "When enabled, scaling will respect original aspect ratio",
450           DEFAULT_FORCE_ASPECT_RATIO,
451           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
452 
453   g_object_class_install_property (gobject_klass, PROP_PIXEL_ASPECT_RATIO,
454       gst_param_spec_fraction ("pixel-aspect-ratio", "Pixel Aspect Ratio",
455           "The pixel aspect ratio of the device", DEFAULT_PAR_N, DEFAULT_PAR_D,
456           G_MAXINT, 1, 1, 1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
457 
458   g_object_class_install_property (gobject_klass, PROP_IGNORE_ALPHA,
459       g_param_spec_boolean ("ignore-alpha", "Ignore Alpha",
460           "When enabled, alpha will be ignored and converted to black",
461           DEFAULT_IGNORE_ALPHA, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
462 
463 #if defined(BUILD_FOR_GTK4)
464   widget_klass->measure = gtk_gst_base_widget_measure;
465 #else
466   widget_klass->get_preferred_width = gtk_gst_base_widget_get_preferred_width;
467   widget_klass->get_preferred_height = gtk_gst_base_widget_get_preferred_height;
468 #endif
469 
470   GST_DEBUG_CATEGORY_INIT (gst_debug_gtk_base_widget, "gtkbasewidget", 0,
471       "GTK Video Base Widget");
472 }
473 
474 void
gtk_gst_base_widget_init(GtkGstBaseWidget * widget)475 gtk_gst_base_widget_init (GtkGstBaseWidget * widget)
476 {
477   widget->force_aspect_ratio = DEFAULT_FORCE_ASPECT_RATIO;
478   widget->par_n = DEFAULT_PAR_N;
479   widget->par_d = DEFAULT_PAR_D;
480   widget->ignore_alpha = DEFAULT_IGNORE_ALPHA;
481 
482   gst_video_info_init (&widget->v_info);
483   gst_video_info_init (&widget->pending_v_info);
484 
485   g_weak_ref_init (&widget->element, NULL);
486   g_mutex_init (&widget->lock);
487 
488   widget->key_controller = gtk_event_controller_key_new (
489 #if !defined(BUILD_FOR_GTK4)
490       GTK_WIDGET (widget)
491 #endif
492       );
493   g_signal_connect (widget->key_controller, "key-pressed",
494       G_CALLBACK (gtk_gst_base_widget_key_event), NULL);
495   g_signal_connect (widget->key_controller, "key-released",
496       G_CALLBACK (gtk_gst_base_widget_key_event), NULL);
497 
498   widget->motion_controller = gtk_event_controller_motion_new (
499 #if !defined(BUILD_FOR_GTK4)
500       GTK_WIDGET (widget)
501 #endif
502       );
503   g_signal_connect (widget->motion_controller, "motion",
504       G_CALLBACK (gtk_gst_base_widget_motion_event), NULL);
505 
506   widget->click_gesture =
507 #if defined(BUILD_FOR_GTK4)
508       gtk_gesture_click_new ();
509 #else
510       gtk_gesture_multi_press_new (GTK_WIDGET (widget));
511 #endif
512   g_signal_connect (widget->click_gesture, "pressed",
513       G_CALLBACK (gtk_gst_base_widget_button_event), NULL);
514   g_signal_connect (widget->click_gesture, "released",
515       G_CALLBACK (gtk_gst_base_widget_button_event), NULL);
516 
517 #if defined(BUILD_FOR_GTK4)
518   /* Otherwise widget in grid will appear as a 1x1px
519    * video which might be misleading for users */
520   gtk_widget_set_hexpand (GTK_WIDGET (widget), TRUE);
521   gtk_widget_set_vexpand (GTK_WIDGET (widget), TRUE);
522 
523   gtk_widget_set_focusable (GTK_WIDGET (widget), TRUE);
524   gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (widget->click_gesture),
525       GDK_BUTTON_PRIMARY);
526 
527   gtk_widget_add_controller (GTK_WIDGET (widget), widget->key_controller);
528   gtk_widget_add_controller (GTK_WIDGET (widget), widget->motion_controller);
529   gtk_widget_add_controller (GTK_WIDGET (widget),
530       GTK_EVENT_CONTROLLER (widget->click_gesture));
531 #endif
532 
533   gtk_widget_set_can_focus (GTK_WIDGET (widget), TRUE);
534 }
535 
536 void
gtk_gst_base_widget_finalize(GObject * object)537 gtk_gst_base_widget_finalize (GObject * object)
538 {
539   GtkGstBaseWidget *widget = GTK_GST_BASE_WIDGET (object);
540 
541   /* GTK4 takes ownership of EventControllers
542    * while GTK3 still needs manual unref */
543 #if !defined(BUILD_FOR_GTK4)
544   g_object_unref (widget->key_controller);
545   g_object_unref (widget->motion_controller);
546   g_object_unref (widget->click_gesture);
547 #endif
548 
549   gst_buffer_replace (&widget->pending_buffer, NULL);
550   gst_buffer_replace (&widget->buffer, NULL);
551   g_mutex_clear (&widget->lock);
552   g_weak_ref_clear (&widget->element);
553 
554   if (widget->draw_id)
555     g_source_remove (widget->draw_id);
556 }
557 
558 void
gtk_gst_base_widget_set_element(GtkGstBaseWidget * widget,GstElement * element)559 gtk_gst_base_widget_set_element (GtkGstBaseWidget * widget,
560     GstElement * element)
561 {
562   g_weak_ref_set (&widget->element, element);
563 }
564 
565 gboolean
gtk_gst_base_widget_set_format(GtkGstBaseWidget * widget,GstVideoInfo * v_info)566 gtk_gst_base_widget_set_format (GtkGstBaseWidget * widget,
567     GstVideoInfo * v_info)
568 {
569   GTK_GST_BASE_WIDGET_LOCK (widget);
570 
571   if (gst_video_info_is_equal (&widget->pending_v_info, v_info)) {
572     GTK_GST_BASE_WIDGET_UNLOCK (widget);
573     return TRUE;
574   }
575 
576   if (!_calculate_par (widget, v_info)) {
577     GTK_GST_BASE_WIDGET_UNLOCK (widget);
578     return FALSE;
579   }
580 
581   widget->pending_resize = TRUE;
582   widget->pending_v_info = *v_info;
583 
584   GTK_GST_BASE_WIDGET_UNLOCK (widget);
585 
586   return TRUE;
587 }
588 
589 void
gtk_gst_base_widget_set_buffer(GtkGstBaseWidget * widget,GstBuffer * buffer)590 gtk_gst_base_widget_set_buffer (GtkGstBaseWidget * widget, GstBuffer * buffer)
591 {
592   /* As we have no type, this is better then no check */
593   g_return_if_fail (GTK_IS_WIDGET (widget));
594 
595   GTK_GST_BASE_WIDGET_LOCK (widget);
596 
597   gst_buffer_replace (&widget->pending_buffer, buffer);
598 
599   if (!widget->draw_id) {
600     widget->draw_id = g_idle_add_full (G_PRIORITY_DEFAULT,
601         (GSourceFunc) _queue_draw, widget, NULL);
602   }
603 
604   GTK_GST_BASE_WIDGET_UNLOCK (widget);
605 }
606