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