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