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