osxvideosink: clear rectangle structures before use
[platform/upstream/gst-plugins-good.git] / sys / osxvideo / osxvideosink.m
1 /* GStreamer
2  * OSX video sink
3  * Copyright (C) 2004-6 Zaheer Abbas Merali <zaheerabbas at merali dot org>
4  * Copyright (C) 2007,2008,2009 Pioneers of the Inevitable <songbird@songbirdnest.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  * The development of this code was made possible due to the involvement of
22  * Pioneers of the Inevitable, the creators of the Songbird Music player.
23  *
24  */
25
26 /**
27  * SECTION:element-osxvideosink
28  *
29  * The OSXVideoSink renders video frames to a MacOSX window. The video output
30  * must be directed to a window embedded in an existing NSApp.
31  *
32  */
33
34 #include "config.h"
35 #include <gst/video/videooverlay.h>
36 #include <gst/video/navigation.h>
37 #include <gst/video/video.h>
38
39 #include "osxvideosink.h"
40 #include <unistd.h>
41 #import "cocoawindow.h"
42
43 GST_DEBUG_CATEGORY (gst_debug_osx_video_sink);
44 #define GST_CAT_DEFAULT gst_debug_osx_video_sink
45
46 #include <pthread.h>
47 extern void _CFRunLoopSetCurrent (CFRunLoopRef rl);
48 extern pthread_t _CFMainPThread;
49
50
51
52 static GstStaticPadTemplate gst_osx_video_sink_sink_template_factory =
53 GST_STATIC_PAD_TEMPLATE ("sink",
54     GST_PAD_SINK,
55     GST_PAD_ALWAYS,
56     GST_STATIC_CAPS ("video/x-raw, "
57         "framerate = (fraction) [ 0, MAX ], "
58         "width = (int) [ 1, MAX ], "
59         "height = (int) [ 1, MAX ], "
60 #if G_BYTE_ORDER == G_BIG_ENDIAN
61        "format = (string) YUY2")
62 #else
63         "format = (string) UYVY")
64 #endif
65     );
66
67 enum
68 {
69   ARG_0,
70   ARG_EMBED,
71   ARG_FORCE_PAR,
72 };
73
74 static void gst_osx_video_sink_osxwindow_destroy (GstOSXVideoSink * osxvideosink);
75 static GMutex _run_loop_check_mutex;
76 static GMutex _run_loop_mutex;
77 static GCond _run_loop_cond;
78 static GstOSXVideoSinkClass *sink_class = NULL;
79 static GstVideoSinkClass *parent_class = NULL;
80
81 /* Helper to trigger calls from the main thread */
82 static void
83 gst_osx_video_sink_call_from_main_thread(GstOSXVideoSink *osxvideosink,
84     NSObject * object, SEL function, NSObject *data, BOOL waitUntilDone)
85 {
86
87   NSThread *thread;
88   NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
89
90   if (sink_class->ns_app_thread == NULL){
91     thread = [NSThread mainThread];
92   } else {
93     thread = sink_class->ns_app_thread;
94   }
95
96   [object performSelector:function onThread:thread
97           withObject:data waitUntilDone:waitUntilDone];
98   [pool release];
99 }
100
101 /* Poll for cocoa events */
102 static void
103 run_ns_app_loop (void) {
104   NSEvent *event;
105   NSAutoreleasePool *pool =[[NSAutoreleasePool alloc] init];
106   NSDate *pollTime = nil;
107
108   /* when running the loop in a thread we want to sleep as long as possible */
109   pollTime = [NSDate distantFuture];
110
111   do {
112       event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:pollTime
113           inMode:NSDefaultRunLoopMode dequeue:YES];
114       [NSApp sendEvent:event];
115     }
116   while (event != nil);
117   [pool release];
118 }
119
120 static void
121 gst_osx_videosink_check_main_run_loop (GstOSXVideoSink *sink)
122 {
123   /* check if the main run loop is running */
124   gboolean is_running;
125
126   /* the easy way */
127   is_running = [[NSRunLoop mainRunLoop] currentMode] != nil;
128   if (is_running) {
129     goto exit;
130   } else {
131     /* the previous check doesn't always work with main loops that run
132      * cocoa's main run loop manually, like the gdk one, giving false
133      * negatives. This check defers a call to the main thread and waits to
134      * be awaken by this function. */
135     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
136     GstOSXVideoSinkObject * object = (GstOSXVideoSinkObject *) sink->osxvideosinkobject;
137     gint64 abstime;
138
139     g_mutex_lock (&_run_loop_mutex);
140     [object performSelectorOnMainThread:
141           @selector(checkMainRunLoop)
142           withObject:nil waitUntilDone:NO];
143     /* Wait 100 ms */
144     abstime = g_get_monotonic_time () + 100 * 1000;
145     is_running = g_cond_wait_until (&_run_loop_cond,
146         &_run_loop_mutex, abstime);
147     g_mutex_unlock (&_run_loop_mutex);
148
149     [pool release];
150   }
151
152 exit:
153   {
154   GST_DEBUG_OBJECT(sink, "The main runloop %s is running",
155       is_running ? "" : " not ");
156   if (is_running) {
157     sink_class->run_loop_state = GST_OSX_VIDEO_SINK_RUN_LOOP_STATE_RUNNING;
158     sink_class->ns_app_thread = [NSThread mainThread];
159   } else {
160     sink_class->run_loop_state = GST_OSX_VIDEO_SINK_RUN_LOOP_STATE_NOT_RUNNING;
161   }
162   }
163 }
164
165 static void
166 gst_osx_video_sink_run_cocoa_loop (GstOSXVideoSink * sink )
167 {
168   /* Cocoa applications require a main runloop running to dispatch UI
169    * events and process deferred calls to the main thread through
170    * perfermSelectorOnMainThread.
171    * Since the sink needs to create it's own Cocoa window when no
172    * external NSView is passed to the sink through the GstVideoOverlay API,
173    * we need to run the cocoa mainloop somehow.
174    * This run loop can only be started once, by the first sink needing it
175    */
176
177   g_mutex_lock (&_run_loop_check_mutex);
178
179   if (sink_class->run_loop_state == GST_OSX_VIDEO_SINK_RUN_LOOP_STATE_UNKNOWN) {
180     gst_osx_videosink_check_main_run_loop (sink);
181   }
182
183   if (sink_class->run_loop_state == GST_OSX_VIDEO_SINK_RUN_LOOP_STATE_RUNNING) {
184     g_mutex_unlock (&_run_loop_check_mutex);
185     return;
186   }
187
188   if (sink_class->ns_app_thread == NULL) {
189     /* run the main runloop in a separate thread */
190
191     /* override [NSThread isMainThread] with our own implementation so that we can
192      * make it believe our dedicated thread is the main thread
193      */
194     Method origIsMainThread = class_getClassMethod([NSThread class],
195         NSSelectorFromString(@"isMainThread"));
196     Method ourIsMainThread = class_getClassMethod([GstOSXVideoSinkObject class],
197         NSSelectorFromString(@"isMainThread"));
198
199     method_exchangeImplementations(origIsMainThread, ourIsMainThread);
200
201     sink_class->ns_app_thread = [[NSThread alloc]
202         initWithTarget:sink->osxvideosinkobject
203         selector:@selector(nsAppThread) object:nil];
204     [sink_class->ns_app_thread start];
205
206     g_mutex_lock (&_run_loop_mutex);
207     g_cond_wait (&_run_loop_cond, &_run_loop_mutex);
208     g_mutex_unlock (&_run_loop_mutex);
209   }
210
211   g_mutex_unlock (&_run_loop_check_mutex);
212 }
213
214 static void
215 gst_osx_video_sink_stop_cocoa_loop (GstOSXVideoSink * osxvideosink)
216 {
217 }
218
219 /* This function handles osx window creation */
220 static gboolean
221 gst_osx_video_sink_osxwindow_create (GstOSXVideoSink * osxvideosink, gint width,
222     gint height)
223 {
224   NSRect rect;
225   GstOSXWindow *osxwindow = NULL;
226   gboolean res = TRUE;
227   NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
228
229   g_return_val_if_fail (GST_IS_OSX_VIDEO_SINK (osxvideosink), FALSE);
230
231   GST_DEBUG_OBJECT (osxvideosink, "Creating new OSX window");
232
233   osxvideosink->osxwindow = osxwindow = g_new0 (GstOSXWindow, 1);
234
235   osxwindow->width = width;
236   osxwindow->height = height;
237   osxwindow->closed = FALSE;
238   osxwindow->internal = FALSE;
239
240   /* Allocate our GstGLView for the window, and then tell the application
241    * about it (hopefully it's listening...) */
242   rect.origin.x = 0.0;
243   rect.origin.y = 0.0;
244   rect.size.width = (float) osxwindow->width;
245   rect.size.height = (float) osxwindow->height;
246   osxwindow->gstview =[[GstGLView alloc] initWithFrame:rect];
247
248
249   gst_osx_video_sink_run_cocoa_loop (osxvideosink);
250   [osxwindow->gstview setMainThread:sink_class->ns_app_thread];
251
252   if (osxvideosink->superview == NULL) {
253     GST_INFO_OBJECT (osxvideosink, "emitting prepare-xwindow-id");
254     gst_video_overlay_prepare_window_handle (GST_VIDEO_OVERLAY (osxvideosink));
255   }
256
257   if (osxvideosink->superview != NULL) {
258     /* prepare-xwindow-id was handled, we have the superview in
259      * osxvideosink->superview. We now add osxwindow->gstview to the superview
260      * from the main thread
261      */
262     GST_INFO_OBJECT (osxvideosink, "we have a superview, adding our view to it");
263     gst_osx_video_sink_call_from_main_thread(osxvideosink, osxwindow->gstview,
264         @selector(addToSuperview:), osxvideosink->superview, NO);
265
266   } else {
267     gst_osx_video_sink_call_from_main_thread(osxvideosink,
268       osxvideosink->osxvideosinkobject,
269       @selector(createInternalWindow), nil, YES);
270     GST_INFO_OBJECT (osxvideosink, "No superview, creating an internal window.");
271   }
272   [osxwindow->gstview setNavigation: GST_NAVIGATION(osxvideosink)];
273   [osxvideosink->osxwindow->gstview setKeepAspectRatio: osxvideosink->keep_par];
274
275   [pool release];
276
277   return res;
278 }
279
280 static void
281 gst_osx_video_sink_osxwindow_destroy (GstOSXVideoSink * osxvideosink)
282 {
283   NSAutoreleasePool *pool;
284
285   g_return_if_fail (GST_IS_OSX_VIDEO_SINK (osxvideosink));
286   pool = [[NSAutoreleasePool alloc] init];
287
288   GST_OBJECT_LOCK (osxvideosink);
289   gst_osx_video_sink_call_from_main_thread(osxvideosink,
290       osxvideosink->osxvideosinkobject,
291       @selector(destroy), (id) nil, YES);
292   GST_OBJECT_UNLOCK (osxvideosink);
293   gst_osx_video_sink_stop_cocoa_loop (osxvideosink);
294   [pool release];
295 }
296
297 /* This function resizes a GstXWindow */
298 static void
299 gst_osx_video_sink_osxwindow_resize (GstOSXVideoSink * osxvideosink,
300     GstOSXWindow * osxwindow, guint width, guint height)
301 {
302   GstOSXVideoSinkObject *object = osxvideosink->osxvideosinkobject;
303
304   NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
305   g_return_if_fail (osxwindow != NULL);
306   g_return_if_fail (GST_IS_OSX_VIDEO_SINK (osxvideosink));
307
308   osxwindow->width = width;
309   osxwindow->height = height;
310
311   GST_DEBUG_OBJECT (osxvideosink, "Resizing window to (%d,%d)", width, height);
312
313   /* Directly resize the underlying view */
314   GST_DEBUG_OBJECT (osxvideosink, "Calling setVideoSize on %p", osxwindow->gstview);
315   gst_osx_video_sink_call_from_main_thread (osxvideosink, object,
316       @selector(resize), (id)nil, YES);
317
318   [pool release];
319 }
320
321 static gboolean
322 gst_osx_video_sink_setcaps (GstBaseSink * bsink, GstCaps * caps)
323 {
324   GstOSXVideoSink *osxvideosink;
325   GstStructure *structure;
326   gboolean res, result = FALSE;
327   gint video_width, video_height;
328
329   osxvideosink = GST_OSX_VIDEO_SINK (bsink);
330
331   GST_DEBUG_OBJECT (osxvideosink, "caps: %" GST_PTR_FORMAT, caps);
332
333   structure = gst_caps_get_structure (caps, 0);
334   res = gst_structure_get_int (structure, "width", &video_width);
335   res &= gst_structure_get_int (structure, "height", &video_height);
336
337   if (!res) {
338     goto beach;
339   }
340
341   GST_DEBUG_OBJECT (osxvideosink, "our format is: %dx%d video",
342       video_width, video_height);
343
344   GST_VIDEO_SINK_WIDTH (osxvideosink) = video_width;
345   GST_VIDEO_SINK_HEIGHT (osxvideosink) = video_height;
346
347   gst_osx_video_sink_osxwindow_resize (osxvideosink, osxvideosink->osxwindow,
348       video_width, video_height);
349
350   gst_video_info_from_caps (&osxvideosink->info, caps);
351
352   result = TRUE;
353
354 beach:
355   return result;
356
357 }
358
359 static GstStateChangeReturn
360 gst_osx_video_sink_change_state (GstElement * element,
361     GstStateChange transition)
362 {
363   GstOSXVideoSink *osxvideosink;
364   GstStateChangeReturn ret;
365
366   osxvideosink = GST_OSX_VIDEO_SINK (element);
367
368   GST_DEBUG_OBJECT (osxvideosink, "%s => %s",
369         gst_element_state_get_name(GST_STATE_TRANSITION_CURRENT (transition)),
370         gst_element_state_get_name(GST_STATE_TRANSITION_NEXT (transition)));
371
372   switch (transition) {
373     case GST_STATE_CHANGE_NULL_TO_READY:
374       break;
375     case GST_STATE_CHANGE_READY_TO_PAUSED:
376       /* Creating our window and our image */
377       GST_VIDEO_SINK_WIDTH (osxvideosink) = 320;
378       GST_VIDEO_SINK_HEIGHT (osxvideosink) = 240;
379       if (!gst_osx_video_sink_osxwindow_create (osxvideosink,
380           GST_VIDEO_SINK_WIDTH (osxvideosink),
381           GST_VIDEO_SINK_HEIGHT (osxvideosink))) {
382         ret = GST_STATE_CHANGE_FAILURE;
383         goto done;
384       }
385       break;
386     default:
387       break;
388   }
389
390   ret = (GST_ELEMENT_CLASS (parent_class))->change_state (element, transition);
391
392   switch (transition) {
393     case GST_STATE_CHANGE_PAUSED_TO_READY:
394       GST_VIDEO_SINK_WIDTH (osxvideosink) = 0;
395       GST_VIDEO_SINK_HEIGHT (osxvideosink) = 0;
396       gst_osx_video_sink_osxwindow_destroy (osxvideosink);
397       break;
398     case GST_STATE_CHANGE_READY_TO_NULL:
399       break;
400     default:
401       break;
402   }
403
404 done:
405   return ret;
406 }
407
408 static GstFlowReturn
409 gst_osx_video_sink_show_frame (GstBaseSink * bsink, GstBuffer * buf)
410 {
411   GstOSXVideoSink *osxvideosink;
412   GstBufferObject* bufferobject;
413   NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
414
415   osxvideosink = GST_OSX_VIDEO_SINK (bsink);
416
417   GST_DEBUG ("show_frame");
418   bufferobject = [[GstBufferObject alloc] initWithBuffer:buf];
419   gst_osx_video_sink_call_from_main_thread(osxvideosink,
420       osxvideosink->osxvideosinkobject,
421       @selector(showFrame:), bufferobject, NO);
422   [pool release];
423   return GST_FLOW_OK;
424 }
425
426 /* Buffer management */
427
428
429
430 /* =========================================== */
431 /*                                             */
432 /*              Init & Class init              */
433 /*                                             */
434 /* =========================================== */
435
436 static void
437 gst_osx_video_sink_set_property (GObject * object, guint prop_id,
438     const GValue * value, GParamSpec * pspec)
439 {
440   GstOSXVideoSink *osxvideosink;
441
442   g_return_if_fail (GST_IS_OSX_VIDEO_SINK (object));
443
444   osxvideosink = GST_OSX_VIDEO_SINK (object);
445
446   switch (prop_id) {
447     case ARG_EMBED:
448       g_warning ("The \"embed\" property of osxvideosink is deprecated and "
449           "has no effect anymore. Use the GstVideoOverlay "
450           "instead.");
451       break;
452     case ARG_FORCE_PAR:
453       osxvideosink->keep_par = g_value_get_boolean(value);
454       if (osxvideosink->osxwindow)
455         [osxvideosink->osxwindow->gstview
456             setKeepAspectRatio: osxvideosink->keep_par];
457       break;
458     default:
459       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
460       break;
461   }
462 }
463
464 static void
465 gst_osx_video_sink_get_property (GObject * object, guint prop_id,
466     GValue * value, GParamSpec * pspec)
467 {
468   GstOSXVideoSink *osxvideosink;
469
470   g_return_if_fail (GST_IS_OSX_VIDEO_SINK (object));
471
472   osxvideosink = GST_OSX_VIDEO_SINK (object);
473
474   switch (prop_id) {
475     case ARG_EMBED:
476       g_value_set_boolean (value, FALSE);
477       break;
478     case ARG_FORCE_PAR:
479       g_value_set_boolean (value, osxvideosink->keep_par);
480       break;
481     default:
482       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
483       break;
484   }
485 }
486
487 static gboolean
488 gst_osx_video_sink_propose_allocation (GstBaseSink * base_sink, GstQuery * query)
489 {
490     gst_query_add_allocation_meta (query,
491         GST_VIDEO_META_API_TYPE, NULL);
492
493     return TRUE;
494 }
495
496 static void
497 gst_osx_video_sink_init (GstOSXVideoSink * sink)
498 {
499   sink->osxwindow = NULL;
500   sink->superview = NULL;
501   sink->osxvideosinkobject = [[GstOSXVideoSinkObject alloc] initWithSink:sink];
502   sink->keep_par = FALSE;
503 }
504
505 static void
506 gst_osx_video_sink_base_init (gpointer g_class)
507 {
508   GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
509
510   gst_element_class_set_static_metadata (element_class, "OSX Video sink",
511       "Sink/Video", "OSX native videosink",
512       "Zaheer Abbas Merali <zaheerabbas at merali dot org>");
513
514   gst_element_class_add_pad_template (element_class,
515       gst_static_pad_template_get (&gst_osx_video_sink_sink_template_factory));
516 }
517
518 static void
519 gst_osx_video_sink_finalize (GObject *object)
520 {
521   GstOSXVideoSink *osxvideosink = GST_OSX_VIDEO_SINK (object);
522
523   if (osxvideosink->superview)
524     [osxvideosink->superview release];
525
526   if (osxvideosink->osxvideosinkobject)
527     [(GstOSXVideoSinkObject*)(osxvideosink->osxvideosinkobject) release];
528
529   G_OBJECT_CLASS (parent_class)->finalize (object);
530 }
531
532 static void
533 gst_osx_video_sink_class_init (GstOSXVideoSinkClass * klass)
534 {
535   GObjectClass *gobject_class;
536   GstElementClass *gstelement_class;
537   GstBaseSinkClass *gstbasesink_class;
538
539   gobject_class = (GObjectClass *) klass;
540   gstelement_class = (GstElementClass *) klass;
541   gstbasesink_class = (GstBaseSinkClass *) klass;
542
543   parent_class = g_type_class_ref (GST_TYPE_VIDEO_SINK);
544   sink_class = klass;
545
546   klass->run_loop_state = GST_OSX_VIDEO_SINK_RUN_LOOP_STATE_UNKNOWN;
547   klass->ns_app_thread = NULL;
548
549   gobject_class->set_property = gst_osx_video_sink_set_property;
550   gobject_class->get_property = gst_osx_video_sink_get_property;
551   gobject_class->finalize = gst_osx_video_sink_finalize;
552
553   gstbasesink_class->set_caps = gst_osx_video_sink_setcaps;
554   gstbasesink_class->preroll = gst_osx_video_sink_show_frame;
555   gstbasesink_class->render = gst_osx_video_sink_show_frame;
556   gstbasesink_class->propose_allocation = gst_osx_video_sink_propose_allocation;
557   gstelement_class->change_state = gst_osx_video_sink_change_state;
558
559   /**
560    * GstOSXVideoSink:embed
561    *
562    * For ABI comatibility onyl, do not use
563    *
564    **/
565
566   g_object_class_install_property (gobject_class, ARG_EMBED,
567       g_param_spec_boolean ("embed", "embed", "For ABI compatiblity only, do not use",
568           FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
569
570   /**
571    * GstOSXVideoSink:force-aspect-ratio
572    *
573    * When enabled, scaling will respect original aspect ratio.
574    *
575    **/
576
577   g_object_class_install_property (gobject_class, ARG_FORCE_PAR,
578       g_param_spec_boolean ("force-aspect-ratio", "force aspect ration",
579           "When enabled, scaling will respect original aspect ration",
580           TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
581 }
582
583 static void
584 gst_osx_video_sink_navigation_send_event (GstNavigation * navigation,
585     GstStructure * structure)
586 {
587   GstOSXVideoSink *osxvideosink = GST_OSX_VIDEO_SINK (navigation);
588   GstPad *peer;
589   GstEvent *event;
590   GstVideoRectangle src = { 0, };
591   GstVideoRectangle dst = { 0, };
592   GstVideoRectangle result;
593   NSRect bounds;
594   gdouble x, y, xscale = 1.0, yscale = 1.0;
595
596   peer = gst_pad_get_peer (GST_VIDEO_SINK_PAD (osxvideosink));
597
598   if (!peer || !osxvideosink->osxwindow)
599     return;
600
601   event = gst_event_new_navigation (structure);
602
603   bounds = [osxvideosink->osxwindow->gstview getDrawingBounds];
604
605   if (osxvideosink->keep_par) {
606     /* We get the frame position using the calculated geometry from _setcaps
607        that respect pixel aspect ratios */
608     src.w = GST_VIDEO_SINK_WIDTH (osxvideosink);
609     src.h = GST_VIDEO_SINK_HEIGHT (osxvideosink);
610     dst.w = bounds.size.width;
611     dst.h = bounds.size.height;
612
613     gst_video_sink_center_rect (src, dst, &result, TRUE);
614     result.x += bounds.origin.x;
615     result.y += bounds.origin.y;
616   } else {
617     result.x = bounds.origin.x;
618     result.y = bounds.origin.y;
619     result.w = bounds.size.width;
620     result.h = bounds.size.height;
621   }
622
623   /* We calculate scaling using the original video frames geometry to include
624      pixel aspect ratio scaling. */
625   xscale = (gdouble) osxvideosink->osxwindow->width / result.w;
626   yscale = (gdouble) osxvideosink->osxwindow->height / result.h;
627
628   /* Converting pointer coordinates to the non scaled geometry */
629   if (gst_structure_get_double (structure, "pointer_x", &x)) {
630     x = MIN (x, result.x + result.w);
631     x = MAX (x - result.x, 0);
632     gst_structure_set (structure, "pointer_x", G_TYPE_DOUBLE,
633         (gdouble) x * xscale, NULL);
634   }
635   if (gst_structure_get_double (structure, "pointer_y", &y)) {
636     y = MIN (y, result.y + result.h);
637     y = MAX (y - result.y, 0);
638     gst_structure_set (structure, "pointer_y", G_TYPE_DOUBLE,
639         (gdouble) y * yscale, NULL);
640   }
641
642   gst_pad_send_event (peer, event);
643   gst_object_unref (peer);
644 }
645
646 static void
647 gst_osx_video_sink_navigation_init (GstNavigationInterface * iface)
648 {
649   iface->send_event = gst_osx_video_sink_navigation_send_event;
650 }
651
652 static void
653 gst_osx_video_sink_set_window_handle (GstVideoOverlay * overlay, guintptr handle_id)
654 {
655   GstOSXVideoSink *osxvideosink = GST_OSX_VIDEO_SINK (overlay);
656   NSView *view = (NSView *) handle_id;
657
658   gst_osx_video_sink_call_from_main_thread(osxvideosink,
659       osxvideosink->osxvideosinkobject,
660       @selector(setView:), view, YES);
661 }
662
663 static void
664 gst_osx_video_sink_xoverlay_init (GstVideoOverlayInterface * iface)
665 {
666   iface->set_window_handle = gst_osx_video_sink_set_window_handle;
667   iface->expose = NULL;
668   iface->handle_events = NULL;
669 }
670
671 /* ============================================================= */
672 /*                                                               */
673 /*                       Public Methods                          */
674 /*                                                               */
675 /* ============================================================= */
676
677 /* =========================================== */
678 /*                                             */
679 /*          Object typing & Creation           */
680 /*                                             */
681 /* =========================================== */
682
683 GType
684 gst_osx_video_sink_get_type (void)
685 {
686   static GType osxvideosink_type = 0;
687
688   if (!osxvideosink_type) {
689     static const GTypeInfo osxvideosink_info = {
690       sizeof (GstOSXVideoSinkClass),
691       gst_osx_video_sink_base_init,
692       NULL,
693       (GClassInitFunc) gst_osx_video_sink_class_init,
694       NULL,
695       NULL,
696       sizeof (GstOSXVideoSink),
697       0,
698       (GInstanceInitFunc) gst_osx_video_sink_init,
699     };
700
701     static const GInterfaceInfo overlay_info = {
702       (GInterfaceInitFunc) gst_osx_video_sink_xoverlay_init,
703       NULL,
704       NULL,
705     };
706
707     static const GInterfaceInfo navigation_info = {
708       (GInterfaceInitFunc) gst_osx_video_sink_navigation_init,
709       NULL,
710       NULL,
711     };
712     osxvideosink_type = g_type_register_static (GST_TYPE_VIDEO_SINK,
713         "GstOSXVideoSink", &osxvideosink_info, 0);
714
715     g_type_add_interface_static (osxvideosink_type, GST_TYPE_VIDEO_OVERLAY,
716         &overlay_info);
717     g_type_add_interface_static (osxvideosink_type, GST_TYPE_NAVIGATION,
718         &navigation_info);
719   }
720
721   return osxvideosink_type;
722 }
723
724 @implementation GstWindowDelegate
725 - (id) initWithSink: (GstOSXVideoSink *) sink
726 {
727   self = [super init];
728   self->osxvideosink = sink;
729   return self;
730 }
731
732 - (void)windowWillClose:(NSNotification *)notification {
733   /* Only handle close events if the window was closed manually by the user
734    * and not becuase of a state change state to READY */
735   if (osxvideosink->osxwindow == NULL) {
736     return;
737   }
738   if (!osxvideosink->osxwindow->closed) {
739     osxvideosink->osxwindow->closed = TRUE;
740     GST_ELEMENT_ERROR (osxvideosink, RESOURCE, NOT_FOUND, ("Output window was closed"), (NULL));
741     gst_osx_video_sink_osxwindow_destroy(osxvideosink);
742   }
743 }
744
745 @end
746
747 @ implementation GstOSXVideoSinkObject
748
749 -(id) initWithSink: (GstOSXVideoSink*) sink
750 {
751   self = [super init];
752   self->osxvideosink = gst_object_ref (sink);
753   return self;
754 }
755
756 -(void) dealloc {
757   gst_object_unref (osxvideosink);
758   [super dealloc];
759 }
760
761 -(void) createInternalWindow
762 {
763   GstOSXWindow *osxwindow = osxvideosink->osxwindow;
764   NSRect rect;
765   unsigned int mask;
766
767   osxwindow->internal = TRUE;
768
769   mask =  NSTitledWindowMask             |
770           NSClosableWindowMask           |
771           NSResizableWindowMask          |
772           NSTexturedBackgroundWindowMask |
773           NSMiniaturizableWindowMask;
774
775   rect.origin.x = 100.0;
776   rect.origin.y = 100.0;
777   rect.size.width = (float) osxwindow->width;
778   rect.size.height = (float) osxwindow->height;
779
780   osxwindow->win =[[[GstOSXVideoSinkWindow alloc]
781                        initWithContentNSRect: rect
782                        styleMask: mask
783                        backing: NSBackingStoreBuffered
784                        defer: NO
785                        screen: nil] retain];
786   GST_DEBUG("VideoSinkWindow created, %p", osxwindow->win);
787   [osxwindow->win orderFrontRegardless];
788   osxwindow->gstview =[osxwindow->win gstView];
789   [osxwindow->win setDelegate:[[GstWindowDelegate alloc]
790       initWithSink:osxvideosink]];
791
792 }
793
794 + (BOOL) isMainThread
795 {
796   /* FIXME: ideally we should return YES only for ->ns_app_thread here */
797   return YES;
798 }
799
800 - (void) setView: (NSView*)view
801 {
802   NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
803
804   if (osxvideosink->superview) {
805     GST_INFO_OBJECT (osxvideosink, "old xwindow id %p", osxvideosink->superview);
806     if (osxvideosink->osxwindow) {
807       [osxvideosink->osxwindow->gstview removeFromSuperview];
808     }
809     [osxvideosink->superview release];
810   }
811   if (osxvideosink->osxwindow != NULL && view != NULL) {
812     if (osxvideosink->osxwindow->internal) {
813       GST_INFO_OBJECT (osxvideosink, "closing internal window");
814       osxvideosink->osxwindow->closed = TRUE;
815       [osxvideosink->osxwindow->win close];
816       [osxvideosink->osxwindow->win release];
817     }
818   }
819
820   GST_INFO_OBJECT (osxvideosink, "set xwindow id %p", view);
821   osxvideosink->superview = [view retain];
822   if (osxvideosink->osxwindow) {
823     [osxvideosink->osxwindow->gstview addToSuperview: osxvideosink->superview];
824     if (view) {
825       osxvideosink->osxwindow->internal = FALSE;
826     }
827   }
828
829   [pool release];
830 }
831
832 - (void) resize
833 {
834   GstOSXWindow *osxwindow = osxvideosink->osxwindow;
835
836   NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
837
838   GST_INFO_OBJECT (osxvideosink, "resizing");
839   NSSize size = {osxwindow->width, osxwindow->height};
840   if (osxwindow->internal) {
841     [osxwindow->win setContentSize:size];
842   }
843   if (osxwindow->gstview) {
844       [osxwindow->gstview setVideoSize :(int)osxwindow->width :(int)osxwindow->height];
845   }
846   GST_INFO_OBJECT (osxvideosink, "done");
847
848   [pool release];
849 }
850
851 - (void) showFrame: (GstBufferObject *) object
852 {
853   GstVideoFrame frame;
854   guint8 *data, *readp, *writep;
855   gint i, active_width, stride;
856   guint8 *texture_buffer;
857   NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
858   GstBuffer *buf = object->buf;
859
860   GST_OBJECT_LOCK (osxvideosink);
861   if (osxvideosink->osxwindow == NULL)
862       goto no_window;
863
864   texture_buffer = (guint8 *) [osxvideosink->osxwindow->gstview getTextureBuffer];
865   if (G_UNLIKELY (texture_buffer == NULL))
866       goto no_texture_buffer;
867
868   if (!gst_video_frame_map (&frame, &osxvideosink->info, buf, GST_MAP_READ))
869       goto no_map;
870
871   data = readp = GST_VIDEO_FRAME_PLANE_DATA (&frame, 0);
872   stride = GST_VIDEO_FRAME_PLANE_STRIDE (&frame, 0);
873   writep = texture_buffer;
874   active_width = GST_VIDEO_SINK_WIDTH (osxvideosink) * sizeof (short);
875   for (i = 0; i < GST_VIDEO_SINK_HEIGHT (osxvideosink); i++) {
876       memcpy (writep, readp, active_width);
877       writep += active_width;
878       readp += stride;
879   }
880   [osxvideosink->osxwindow->gstview displayTexture];
881
882   gst_video_frame_unmap (&frame);
883
884 out:
885   GST_OBJECT_UNLOCK (osxvideosink);
886   [object release];
887
888   [pool release];
889   return;
890
891 no_map:
892   GST_WARNING_OBJECT (osxvideosink, "couldn't map frame");
893   goto out;
894
895 no_window:
896   GST_WARNING_OBJECT (osxvideosink, "not showing frame since we have no window (!?)");
897   goto out;
898
899 no_texture_buffer:
900   GST_ELEMENT_ERROR (osxvideosink, RESOURCE, WRITE, (NULL),
901           ("the texture buffer is NULL"));
902   goto out;
903 }
904
905 -(void) destroy
906 {
907   NSAutoreleasePool *pool;
908   GstOSXWindow *osxwindow;
909
910   pool = [[NSAutoreleasePool alloc] init];
911
912   osxwindow = osxvideosink->osxwindow;
913   osxvideosink->osxwindow = NULL;
914
915   if (osxwindow) {
916     if (osxvideosink->superview) {
917       [osxwindow->gstview removeFromSuperview];
918     }
919     [osxwindow->gstview release];
920     if (osxwindow->internal) {
921       if (!osxwindow->closed) {
922         osxwindow->closed = TRUE;
923         [osxwindow->win close];
924         [osxwindow->win release];
925       }
926     }
927     g_free (osxwindow);
928   }
929   [pool release];
930 }
931
932 -(void) nsAppThread
933 {
934   NSAutoreleasePool *pool;
935
936   /* set the main runloop as the runloop for the current thread. This has the
937    * effect that calling NSApp nextEventMatchingMask:untilDate:inMode:dequeue
938    * runs the main runloop.
939    */
940   _CFRunLoopSetCurrent(CFRunLoopGetMain());
941
942   /* this is needed to make IsMainThread checks in core foundation work from the
943    * current thread
944    */
945   _CFMainPThread = pthread_self();
946
947   pool = [[NSAutoreleasePool alloc] init];
948
949   [NSApplication sharedApplication];
950   [NSApp finishLaunching];
951
952   g_mutex_lock (&_run_loop_mutex);
953   g_cond_signal (&_run_loop_cond);
954   g_mutex_unlock (&_run_loop_mutex);
955
956   /* run the loop */
957   run_ns_app_loop ();
958
959   [pool release];
960 }
961
962 -(void) checkMainRunLoop
963 {
964   g_mutex_lock (&_run_loop_mutex);
965   g_cond_signal (&_run_loop_cond);
966   g_mutex_unlock (&_run_loop_mutex);
967 }
968
969 @end
970
971 @ implementation GstBufferObject
972 -(id) initWithBuffer: (GstBuffer*) buffer
973 {
974   self = [super init];
975   gst_buffer_ref(buffer);
976   self->buf = buffer;
977   return self;
978 }
979
980 -(void) dealloc{
981   gst_buffer_unref(buf);
982   [super dealloc];
983 }
984 @end
985
986 static gboolean
987 plugin_init (GstPlugin * plugin)
988 {
989
990   if (!gst_element_register (plugin, "osxvideosink",
991           GST_RANK_MARGINAL, GST_TYPE_OSX_VIDEO_SINK))
992     return FALSE;
993
994   GST_DEBUG_CATEGORY_INIT (gst_debug_osx_video_sink, "osxvideosink", 0,
995       "osxvideosink element");
996
997   return TRUE;
998 }
999
1000 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
1001     GST_VERSION_MINOR,
1002     osxvideo,
1003     "OSX native video output plugin",
1004     plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)